Skip to content

Commit

Permalink
first pass at generating source maps
Browse files Browse the repository at this point in the history
  • Loading branch information
alltom committed Jul 24, 2013
1 parent 28bd801 commit 5b20ef2
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 29 deletions.
34 changes: 28 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
var parse = require('esprima').parse;
var SourceNode = require("source-map").SourceNode;

var objectKeys = Object.keys || function (obj) {
var keys = [];
for (var key in obj) keys.push(key);
Expand All @@ -15,6 +17,10 @@ var isArray = Array.isArray || function (xs) {
return Object.prototype.toString.call(xs) === '[object Array]';
};

var base64 = function (str) {
return new Buffer(str).toString('base64');
}

module.exports = function (src, opts, fn) {
if (typeof opts === 'function') {
fn = opts;
Expand All @@ -27,19 +33,25 @@ module.exports = function (src, opts, fn) {
}
src = src === undefined ? opts.source : src;
opts.range = true;
opts.loc = true;
if (typeof src !== 'string') src = String(src);

var ast = parse(src, opts);

var result = {
chunks : src.split(''),
toString : function () { return result.chunks.join('') },
toString : function () {
var root = new SourceNode(null, null, null, result.chunks);
root.setSourceContent(opts.sourceFilename || "in.js", src);
var sm = root.toStringWithSourceMap({ file: opts.generatedFilename || "out.js" });
return sm.code + "\n//@ sourceMappingURL=data:application/json;base64," + base64(sm.map.toString()) + "\n";
},
inspect : function () { return result.toString() }
};
var index = 0;

(function walk (node, parent) {
insertHelpers(node, parent, result.chunks);
insertHelpers(node, parent, result.chunks, opts);

forEach(objectKeys(node), function (key) {
if (key === 'parent') return;
Expand All @@ -53,7 +65,7 @@ module.exports = function (src, opts, fn) {
});
}
else if (child && typeof child.type === 'string') {
insertHelpers(child, node, result.chunks);
insertHelpers(child, node, result.chunks, opts);
walk(child, node);
}
});
Expand All @@ -63,7 +75,7 @@ module.exports = function (src, opts, fn) {
return result;
};

function insertHelpers (node, parent, chunks) {
function insertHelpers (node, parent, chunks, opts) {
if (!node.range) return;

node.parent = parent;
Expand All @@ -74,6 +86,12 @@ function insertHelpers (node, parent, chunks) {
).join('');
};

node.sourceNodes = function () {
return chunks.slice(
node.range[0], node.range[1]
);
};

if (node.update && typeof node.update === 'object') {
var prev = node.update;
forEach(objectKeys(prev), function (key) {
Expand All @@ -85,8 +103,12 @@ function insertHelpers (node, parent, chunks) {
node.update = update;
}

function update (s) {
chunks[node.range[0]] = s;
function update () {
chunks[node.range[0]] = new SourceNode(
node.loc.start.line,
node.loc.start.column,
opts.sourceFilename || "in.js",
Array.prototype.slice.apply(arguments));
for (var i = node.range[0] + 1; i < node.range[1]; i++) {
chunks[i] = '';
}
Expand Down
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name" : "falafel",
"description" : "transform the ast on a recursive walk",
"name" : "falafel-map",
"description" : "transform the ast on a recursive walk, generating a source map",
"version" : "0.3.1",
"repository" : {
"type" : "git",
"url" : "git://github.com/substack/node-falafel.git"
"url" : "git://github.com/alltom/node-falafel-map.git"
},
"main" : "index.js",
"keywords" : [
Expand All @@ -22,6 +22,9 @@
"scripts" : {
"test" : "tape test/*.js"
},
"dependencies" : {
"source-map" : "0.1.x"
},
"bundledDependencies" : [ "esprima" ],
"devDependencies" : {
"tape" : "~1.0.4"
Expand All @@ -35,6 +38,13 @@
"email" : "mail@substack.net",
"url" : "http://substack.net"
},
"contributors" : [
{
"name" : "Tom Lieber",
"email" : "tom@alltom.com",
"url" : "http://alltom.com/"
}
],
"testling" : {
"files" : "test/*.js",
"browsers" : {
Expand Down
49 changes: 29 additions & 20 deletions readme.markdown
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
# falafel
# falafel-map

Transform the [ast](http://en.wikipedia.org/wiki/Abstract_syntax_tree) on a
recursive walk.

[![browser support](http://ci.testling.com/substack/node-falafel.png)](http://ci.testling.com/substack/node-falafel)

[![build status](https://secure.travis-ci.org/substack/node-falafel.png)](http://travis-ci.org/substack/node-falafel)

This module is like [burrito](https://github.com/substack/node-burrito),
except that it uses [esprima](http://esprima.org) instead of
[uglify](https://github.com/mishoo/UglifyJS)
for friendlier-looking ast nodes.
This module is like [falafel](https://github.com/substack/node-falafel),
except that it uses [source-map](https://github.com/mozilla/source-map) for
appending source maps to processed scripts.

![falafel döner](http://substack.net/images/falafel.png)

Expand All @@ -21,15 +16,15 @@ for friendlier-looking ast nodes.
Put a function wrapper around all array literals.

``` js
var falafel = require('falafel');
var falafelMap = require('falafel-map');

var src = '(' + function () {
var xs = [ 1, 2, [ 3, 4 ] ];
var ys = [ 5, 6 ];
console.dir([ xs, ys ]);
} + ')()';

var output = falafel(src, function (node) {
var output = falafelMap(src, function (node) {
if (node.type === 'ArrayExpression') {
node.update('fn(' + node.source() + ')');
}
Expand All @@ -45,6 +40,7 @@ output:
var ys = fn([ 5, 6 ]);
console.dir(fn([ xs, ys ]));
})()
//@ sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0LmpzIiwic291cmNlcyI6WyJpbi5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFDLENBQUEsQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFZLENBQUE7QUFBQSxDQUFBLENBQUEsQ0FBQSxDQUNULENBQUEsQ0FBQSxDQUFBLENBQUksQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFLLDBCQUFULENBRFM7QUFBQSxDQUFBLENBQUEsQ0FBQSxDQUVULENBQUEsQ0FBQSxDQUFBLENBQUksQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFLLFlBQVQsQ0FGUztBQUFBLENBQUEsQ0FBQSxDQUFBLENBR1QsQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFRLENBQUEsQ0FBQSxDQUFSLENBQVksY0FBWixDQUFBLENBSFM7QUFBQSxDQUFiLENBQUEsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiAoKSB7XG4gICAgdmFyIHhzID0gWyAxLCAyLCBbIDMsIDQgXSBdO1xuICAgIHZhciB5cyA9IFsgNSwgNiBdO1xuICAgIGNvbnNvbGUuZGlyKFsgeHMsIHlzIF0pO1xufSkoKSJdfQ==
```

## custom keyword
Expand All @@ -54,14 +50,14 @@ Creating custom keywords is super simple!
This example creates a new `beep` keyword that uppercases its arguments:

``` js
var falafel = require('falafel');
var falafelMap = require('falafel-map');
var src = 'console.log(beep "boop", "BOOP");';

function isKeyword (id) {
if (id === 'beep') return true;
}

var output = falafel(src, { isKeyword: isKeyword }, function (node) {
var output = falafelMap(src, { isKeyword: isKeyword }, function (node) {
if (node.type === 'UnaryExpression'
&& node.keyword === 'beep') {
node.update(
Expand All @@ -77,6 +73,7 @@ Now the source string `console.log(beep "boop", "BOOP");` is converted to:
```
$ node example/keyword.js
console.log(String("boop").toUpperCase(), "BOOP");
//@ sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0LmpzIiwic291cmNlcyI6WyJpbi5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxDQUFBLENBQUEsQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUFBLENBQVEsQ0FBQSxDQUFBLENBQVIsQ0FBWSw0QkFBWixDQUFBLENBQXlCLENBQUEsQ0FBQSxDQUFBLENBQUEsQ0FBQSxDQUF6QixDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiY29uc29sZS5sb2coYmVlcCBcImJvb3BcIiwgXCJCT09QXCIpOyJdfQ==
```

which we can execute:
Expand All @@ -91,10 +88,10 @@ Neat!
# methods

``` js
var falafel = require('falafel')
var falafelMap = require('falafel-map')
```

## falafel(src, opts={}, fn)
## falafelMap(src, opts={}, fn)

Transform the string source `src` with the function `fn`, returning a
string-like transformed output object.
Expand All @@ -111,8 +108,11 @@ returned and still capture the output.

Instead of passing a `src` you can also use `opts.source`.

All of the `opts` will be passed directly to esprima except for `'range'` which
is always turned on because falafel needs it.
All of the `opts` will be passed directly to esprima except for `'range'` and
`'loc'`, which are always turned on because falafel-map needs them.

`'sourceFilename'` and `'generatedFilename'` can be used to control the names
used in the map, and default to `in.js` and `out.js`, respectively.

Some of the options you might want from esprima includes:
`'loc'`, `'raw'`, `'comment'`, `'tokens'`, and `'tolerant'`.
Expand All @@ -138,9 +138,18 @@ update functions on children from parent nodes.
Return the source for the given node, including any modifications made to
children nodes.

## node.update(s)
## node.sourceNodes()

Return the array of strings and SourceNodes for the given esprima node.

## node.update()

Replace the source nodes for the given node with the arguments to `update`,
be they strings or SourceNodes.

Transform the source for the present node to the string `s`.
To maintain source mappings to children, pass the result of `node.sourceNodes()`
as one of the arguments to this function. For example:
`node.update("[", node.sourceNodes(), "]")`.

Note that in `'ForStatement'` node types, there is an existing subnode called
`update`. For those nodes all the properties are copied over onto the
Expand All @@ -155,7 +164,7 @@ Reference to the parent element or `null` at the root element.
With [npm](http://npmjs.org) do:

```
npm install falafel
npm install falafel-map
```

# license
Expand Down

0 comments on commit 5b20ef2

Please sign in to comment.