Merge pull request #902 from wycats/source-map

Generate Source Maps
This commit is contained in:
Kevin Decker
2014-11-08 18:04:56 -06:00
12 changed files with 459 additions and 168 deletions
+4
View File
@@ -7,6 +7,10 @@ var optimist = require('optimist')
'description': 'Output File',
'alias': 'output'
},
'map': {
'type': 'string',
'description': 'Source Map File'
},
'a': {
'type': 'boolean',
'description': 'Exports amd style (require.js)',
+151
View File
@@ -0,0 +1,151 @@
import {isArray} from "../utils";
try {
var SourceMap = require('source-map'),
SourceNode = SourceMap.SourceNode;
} catch (err) {
/* istanbul ignore next: tested but not covered in istanbul due to dist build */
SourceNode = function(line, column, srcFile, chunks) {
this.src = '';
if (chunks) {
this.add(chunks);
}
};
/* istanbul ignore next */
SourceNode.prototype = {
add: function(chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src += chunks;
},
prepend: function(chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src = chunks + this.src;
},
toStringWithSourceMap: function() {
return {code: this.toString()};
},
toString: function() {
return this.src;
}
};
}
function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) {
var ret = [];
for (var i = 0, len = chunk.length; i < len; i++) {
ret.push(codeGen.wrap(chunk[i], loc));
}
return ret;
} else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
// Handle primitives that the SourceNode will throw up on
return chunk+'';
}
return chunk;
}
function CodeGen(srcFile) {
this.srcFile = srcFile;
this.source = [];
}
CodeGen.prototype = {
prepend: function(source, loc) {
this.source.unshift(this.wrap(source, loc));
},
push: function(source, loc) {
this.source.push(this.wrap(source, loc));
},
merge: function() {
var source = this.empty();
this.each(function(line) {
source.add([' ', line, '\n']);
});
return source;
},
each: function(iter) {
for (var i = 0, len = this.source.length; i < len; i++) {
iter(this.source[i]);
}
},
empty: function(loc) {
loc = loc || this.currentLocation || {};
return new SourceNode(loc.firstLine, loc.firstColumn, this.srcFile);
},
wrap: function(chunk, loc) {
if (chunk instanceof SourceNode) {
return chunk;
}
loc = loc || this.currentLocation || {};
chunk = castChunk(chunk, this, loc);
return new SourceNode(loc.firstLine, loc.firstColumn, this.srcFile, chunk);
},
functionCall: function(fn, type, params) {
params = this.generateList(params);
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
},
quotedString: function(str) {
return '"' + str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
},
objectLiteral: function(obj) {
var pairs = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
pairs.push([this.quotedString(key), ':', castChunk(obj[key], this)]);
}
}
var ret = this.generateList(pairs);
ret.prepend('{');
ret.add('}');
return ret;
},
generateList: function(entries, loc) {
var ret = this.empty(loc);
for (var i = 0, len = entries.length; i < len; i++) {
if (i) {
ret.add(',');
}
ret.add(castChunk(entries[i], this, loc));
}
return ret;
},
generateArray: function(entries, loc) {
var ret = this.generateList(entries, loc);
ret.prepend('[');
ret.add(']');
return ret;
}
};
export default CodeGen;
+49 -45
View File
@@ -130,36 +130,36 @@ Compiler.prototype = {
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('blockValue', sexpr.id.original);
this.opcode('pushProgram', block, program);
this.opcode('pushProgram', block, inverse);
this.opcode('emptyHash', block);
this.opcode('blockValue', block, sexpr.id.original);
} else {
this.ambiguousSexpr(sexpr, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('ambiguousBlockValue');
this.opcode('pushProgram', block, program);
this.opcode('pushProgram', block, inverse);
this.opcode('emptyHash', block);
this.opcode('ambiguousBlockValue', block);
}
this.opcode('append');
this.opcode('append', block);
},
hash: function(hash) {
var pairs = hash.pairs, i, l;
this.opcode('pushHash');
this.opcode('pushHash', hash);
for(i=0, l=pairs.length; i<l; i++) {
this.pushParam(pairs[i][1]);
}
while(i--) {
this.opcode('assignToHash', pairs[i][0]);
this.opcode('assignToHash', hash, pairs[i][0]);
}
this.opcode('popHash');
this.opcode('popHash', hash);
},
partial: function(partial) {
@@ -169,28 +169,28 @@ Compiler.prototype = {
if (partial.hash) {
this.accept(partial.hash);
} else {
this.opcode('push', 'undefined');
this.opcode('pushLiteral', partial, 'undefined');
}
if (partial.context) {
this.accept(partial.context);
} else {
this.opcode('getContext', 0);
this.opcode('pushContext');
this.opcode('getContext', partial, 0);
this.opcode('pushContext', partial);
}
var indent = partial.indent || '';
if (this.options.preventIndent && indent) {
this.opcode('appendContent', indent);
this.opcode('appendContent', partial, indent);
indent = '';
}
this.opcode('invokePartial', partialName.name, indent);
this.opcode('append');
this.opcode('invokePartial', partial, partialName.name, indent);
this.opcode('append', partial);
},
content: function(content) {
if (content.string) {
this.opcode('appendContent', content.string);
this.opcode('appendContent', content, content.string);
}
},
@@ -198,9 +198,9 @@ Compiler.prototype = {
this.sexpr(mustache.sexpr);
if(mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
this.opcode('appendEscaped', mustache);
} else {
this.opcode('append');
this.opcode('append', mustache);
}
},
@@ -209,14 +209,14 @@ Compiler.prototype = {
name = id.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', id.depth);
this.opcode('getContext', sexpr, id.depth);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('pushProgram', sexpr, program);
this.opcode('pushProgram', sexpr, inverse);
this.ID(id);
this.opcode('invokeAmbiguous', name, isBlock);
this.opcode('invokeAmbiguous', sexpr, name, isBlock);
},
simpleSexpr: function(sexpr) {
@@ -229,11 +229,11 @@ Compiler.prototype = {
} else {
// Simplified ID for `this`
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('pushContext');
this.opcode('getContext', sexpr, id.depth);
this.opcode('pushContext', sexpr);
}
this.opcode('resolvePossibleLambda');
this.opcode('resolvePossibleLambda', sexpr);
},
helperSexpr: function(sexpr, program, inverse) {
@@ -242,14 +242,14 @@ Compiler.prototype = {
name = id.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
this.opcode('invokeKnownHelper', sexpr, params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
} else {
id.falsy = true;
this.ID(id);
this.opcode('invokeHelper', params.length, id.original, id.isSimple);
this.opcode('invokeHelper', sexpr, params.length, id.original, id.isSimple);
}
},
@@ -267,39 +267,43 @@ Compiler.prototype = {
ID: function(id) {
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('getContext', id, id.depth);
var name = id.parts[0];
if (!name) {
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
this.opcode('pushContext');
this.opcode('pushContext', id);
} else {
this.opcode('lookupOnContext', id.parts, id.falsy, id.isScoped);
this.opcode('lookupOnContext', id, id.parts, id.falsy, id.isScoped);
}
},
DATA: function(data) {
this.options.data = true;
this.opcode('lookupData', data.id.depth, data.id.parts);
this.opcode('lookupData', data, data.id.depth, data.id.parts);
},
STRING: function(string) {
this.opcode('pushString', string.string);
this.opcode('pushString', string, string.string);
},
NUMBER: function(number) {
this.opcode('pushLiteral', number.number);
this.opcode('pushLiteral', number, number.number);
},
BOOLEAN: function(bool) {
this.opcode('pushLiteral', bool.bool);
this.opcode('pushLiteral', bool, bool.bool);
},
comment: function() {},
// HELPERS
opcode: function(name) {
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1) });
opcode: function(name, node) {
var loc = {
firstLine: node.firstLine, firstColumn: node.firstColumn,
lastLine: node.lastLine, lastColumn: node.lastColumn
};
this.opcodes.push({ opcode: name, args: slice.call(arguments, 2), loc: loc });
},
addDepth: function(depth) {
@@ -344,8 +348,8 @@ Compiler.prototype = {
if(val.depth) {
this.addDepth(val.depth);
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
this.opcode('getContext', val, val.depth || 0);
this.opcode('pushStringParam', val, val.stringModeValue, val.type);
if (val.type === 'sexpr') {
// Subexpressions get evaluated and passed in
@@ -354,7 +358,7 @@ Compiler.prototype = {
}
} else {
if (this.trackIds) {
this.opcode('pushId', val.type, val.idName || val.stringModeValue);
this.opcode('pushId', val, val.type, val.idName || val.stringModeValue);
}
this.accept(val);
}
@@ -364,13 +368,13 @@ Compiler.prototype = {
var params = sexpr.params;
this.pushParams(params);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('pushProgram', sexpr, program);
this.opcode('pushProgram', sexpr, inverse);
if (sexpr.hash) {
this.hash(sexpr.hash);
} else {
this.opcode('emptyHash');
this.opcode('emptyHash', sexpr);
}
return params;
+128 -99
View File
@@ -1,5 +1,7 @@
import { COMPILER_REVISION, REVISION_CHANGES } from "../base";
import Exception from "../exception";
import {isArray} from "../utils";
import CodeGen from "./code-gen";
function Literal(value) {
this.value = value;
@@ -12,15 +14,15 @@ JavaScriptCompiler.prototype = {
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name /* , type*/) {
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
return parent + "." + name;
return [parent, ".", name];
} else {
return parent + "['" + name + "']";
return [parent, "['", name, "']"];
}
},
depthedLookup: function(name) {
this.aliases.lookup = 'this.lookup';
return 'lookup(depths, "' + name + '")';
return ['lookup(depths, "', name, '")'];
},
compilerInfo: function() {
@@ -29,15 +31,23 @@ JavaScriptCompiler.prototype = {
return [revision, versions];
},
appendToBuffer: function(string) {
appendToBuffer: function(string, location, explicit) {
// Force a string as this simplifies the merge logic.
if (!isArray(string)) {
string = [string];
}
string = this.source.wrap(string, location);
if (this.environment.isSimple) {
return "return " + string + ";";
return ['return ', string, ';'];
} else if (explicit) {
// This is a case where the buffer operation occurs as a child of another
// construct, generally braces. We have to explicitly output these buffer
// operations to ensure that the emitted code goes in the correct location.
return ['buffer += ', string, ';'];
} else {
return {
appendToBuffer: true,
content: string,
toString: function() { return "buffer += " + string + ";"; }
};
string.appendToBuffer = true;
return string;
}
},
@@ -78,16 +88,20 @@ JavaScriptCompiler.prototype = {
var opcodes = environment.opcodes,
opcode,
firstLoc,
i,
l;
for (i = 0, l = opcodes.length; i < l; i++) {
opcode = opcodes[i];
this.source.currentLocation = opcode.loc;
firstLoc = firstLoc || opcode.loc;
this[opcode.opcode].apply(this, opcode.args);
}
// Flush any trailing content that might be pending.
this.source.currentLocation = firstLoc;
this.pushSource('');
/* istanbul ignore next */
@@ -123,7 +137,16 @@ JavaScriptCompiler.prototype = {
if (!asObject) {
ret.compiler = JSON.stringify(ret.compiler);
this.source.currentLocation = {firstLine: 1, firstColumn: 0};
ret = this.objectLiteral(ret);
if (options.srcName) {
ret = ret.toStringWithSourceMap({file: options.destName});
ret.map = ret.map && ret.map.toString();
} else {
ret = ret.toString();
}
}
return ret;
@@ -136,7 +159,7 @@ JavaScriptCompiler.prototype = {
// track the last context pushed into place to allow skipping the
// getContext opcode when it would be a noop
this.lastContext = 0;
this.source = [];
this.source = new CodeGen(this.options.srcName);
},
createFunctionContext: function(asObject) {
@@ -168,59 +191,67 @@ JavaScriptCompiler.prototype = {
return Function.apply(this, params);
} else {
return 'function(' + params.join(',') + ') {\n ' + source + '}';
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
}
},
mergeSource: function(varDeclarations) {
var source = '',
buffer,
var isSimple = this.environment.isSimple,
appendOnly = !this.forceBuffer,
appendFirst;
appendFirst,
for (var i = 0, len = this.source.length; i < len; i++) {
var line = this.source[i];
sourceSeen,
bufferStart,
bufferEnd;
this.source.each(function(line) {
if (line.appendToBuffer) {
if (buffer) {
buffer = buffer + '\n + ' + line.content;
if (bufferStart) {
line.prepend(' + ');
} else {
buffer = line.content;
bufferStart = line;
}
bufferEnd = line;
} else {
if (buffer) {
if (!source) {
if (bufferStart) {
if (!sourceSeen) {
appendFirst = true;
source = buffer + ';\n ';
} else {
source += 'buffer += ' + buffer + ';\n ';
bufferStart.prepend('buffer += ');
}
buffer = undefined;
bufferEnd.add(';');
bufferStart = bufferEnd = undefined;
}
source += line + '\n ';
if (!this.environment.isSimple) {
sourceSeen = true;
if (!isSimple) {
appendOnly = false;
}
}
}
});
if (appendOnly) {
if (buffer || !source) {
source += 'return ' + (buffer || '""') + ';\n';
if (bufferStart) {
bufferStart.prepend('return ');
bufferEnd.add(';');
} else {
this.source.push('return "";');
}
} else {
varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer());
if (buffer) {
source += 'return buffer + ' + buffer + ';\n';
if (bufferStart) {
bufferStart.prepend('return buffer + ');
bufferEnd.add(';');
} else {
source += 'return buffer;\n';
this.source.push('return buffer;');
}
}
if (varDeclarations) {
source = 'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n ') + source;
this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n '));
}
return source;
return this.source.merge();
},
// [blockValue]
@@ -241,7 +272,7 @@ JavaScriptCompiler.prototype = {
var blockName = this.popStack();
params.splice(1, 0, blockName);
this.push('blockHelperMissing.call(' + params.join(', ') + ')');
this.push(this.source.functionCall('blockHelperMissing', 'call', params));
},
// [ambiguousBlockValue]
@@ -262,7 +293,10 @@ JavaScriptCompiler.prototype = {
var current = this.topStack();
params.splice(1, 0, current);
this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
this.pushSource([
'if (!', this.lastHelper, ') { ',
current, ' = ', this.source.functionCall('blockHelperMissing', 'call', params),
'}']);
},
// [appendContent]
@@ -274,6 +308,8 @@ JavaScriptCompiler.prototype = {
appendContent: function(content) {
if (this.pendingContent) {
content = this.pendingContent + content;
} else {
this.pendingLocation = this.source.currentLocation;
}
this.pendingContent = content;
@@ -291,15 +327,15 @@ JavaScriptCompiler.prototype = {
append: function() {
if (this.isInline()) {
this.replaceStack(function(current) {
return ' != null ? ' + current + ' : ""';
return [' != null ? ', current, ' : ""'];
});
this.pushSource(this.appendToBuffer(this.popStack()));
} else {
var local = this.popStack();
this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }');
this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
if (this.environment.isSimple) {
this.pushSource("else { " + this.appendToBuffer("''") + " }");
this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
}
}
},
@@ -313,7 +349,7 @@ JavaScriptCompiler.prototype = {
appendEscaped: function() {
this.aliases.escapeExpression = 'this.escapeExpression';
this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
this.pushSource(this.appendToBuffer(['escapeExpression(', this.popStack(), ')']));
},
// [getContext]
@@ -363,10 +399,10 @@ JavaScriptCompiler.prototype = {
// We want to ensure that zero and false are handled properly if the context (falsy flag)
// needs to have the special handling for these values.
if (!falsy) {
return ' != null ? ' + lookup + ' : ' + current;
return [' != null ? ', lookup, ' : ', current];
} else {
// Otherwise we can use generic falsy handling
return ' && ' + lookup;
return [' && ', lookup];
}
});
}
@@ -389,7 +425,7 @@ JavaScriptCompiler.prototype = {
var len = parts.length;
for (var i = 0; i < len; i++) {
this.replaceStack(function(current) {
return ' && ' + this.nameLookup(current, parts[i], 'data');
return [' && ', this.nameLookup(current, parts[i], 'data')];
});
}
},
@@ -404,7 +440,7 @@ JavaScriptCompiler.prototype = {
resolvePossibleLambda: function() {
this.aliases.lambda = 'this.lambda';
this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')');
this.push(['lambda(', this.popStack(), ', ', this.contextName(0), ')']);
},
// [pushStringParam]
@@ -452,14 +488,14 @@ JavaScriptCompiler.prototype = {
this.hash = this.hashes.pop();
if (this.trackIds) {
this.push('{' + hash.ids.join(',') + '}');
this.push(this.objectLiteral(hash.ids));
}
if (this.stringParams) {
this.push('{' + hash.contexts.join(',') + '}');
this.push('{' + hash.types.join(',') + '}');
this.push(this.objectLiteral(hash.contexts));
this.push(this.objectLiteral(hash.types));
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
this.push(this.objectLiteral(hash.values));
},
// [pushString]
@@ -472,17 +508,6 @@ JavaScriptCompiler.prototype = {
this.pushStackLiteral(this.quotedString(string));
},
// [push]
//
// On stack, before: ...
// On stack, after: expr, ...
//
// Push an expression onto the stack
push: function(expr) {
this.inlineStack.push(expr);
return expr;
},
// [pushLiteral]
//
// On stack, before: ...
@@ -525,9 +550,13 @@ JavaScriptCompiler.prototype = {
var nonHelper = this.popStack();
var helper = this.setupHelper(paramSize, name);
var simple = isSimple ? [helper.name, ' || '] : '';
var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing';
this.push('((' + lookup + ').call(' + helper.callParams + '))');
this.push(
this.source.functionCall(
['('].concat(simple, nonHelper, ' || ', 'helperMissing)'),
'call',
helper.callParams));
},
// [invokeKnownHelper]
@@ -539,7 +568,7 @@ JavaScriptCompiler.prototype = {
// so a `helperMissing` fallback is not required.
invokeKnownHelper: function(paramSize, name) {
var helper = this.setupHelper(paramSize, name);
this.push(helper.name + ".call(" + helper.callParams + ")");
this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
},
// [invokeAmbiguous]
@@ -566,10 +595,12 @@ JavaScriptCompiler.prototype = {
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
this.push(
'((helper = (helper = ' + helperName + ' || ' + nonHelper + ') != null ? helper : helperMissing'
+ (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),'
+ '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))');
this.push([
'((helper = (helper = ', helperName, ' || ', nonHelper, ') != null ? helper : helperMissing',
(helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
'(typeof helper === functionType ? ',
this.source.functionCall('helper','call', helper.callParams), ' : helper))'
]);
},
// [invokePartial]
@@ -591,7 +622,7 @@ JavaScriptCompiler.prototype = {
params.push('depths');
}
this.push("this.invokePartial(" + params.join(", ") + ")");
this.push(this.source.functionCall('this.invokePartial', '', params));
},
// [assignToHash]
@@ -616,15 +647,15 @@ JavaScriptCompiler.prototype = {
var hash = this.hash;
if (context) {
hash.contexts.push("'" + key + "': " + context);
hash.contexts[key] = context;
}
if (type) {
hash.types.push("'" + key + "': " + type);
hash.types[key] = type;
}
if (id) {
hash.ids.push("'" + key + "': " + id);
hash.ids[key] = id;
}
hash.values.push("'" + key + "': (" + value + ")");
hash.values[key] = value;
},
pushId: function(type, name) {
@@ -696,13 +727,23 @@ JavaScriptCompiler.prototype = {
}
},
push: function(expr) {
if (!(expr instanceof Literal)) {
expr = this.source.wrap(expr);
}
this.inlineStack.push(expr);
return expr;
},
pushStackLiteral: function(item) {
this.push(new Literal(item));
},
pushSource: function(source) {
if (this.pendingContent) {
this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
this.source.push(
this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
this.pendingContent = undefined;
}
@@ -712,7 +753,7 @@ JavaScriptCompiler.prototype = {
},
replaceStack: function(callback) {
var prefix = '',
var prefix = ['('],
inline = this.isInline(),
stack,
createdStack,
@@ -728,14 +769,15 @@ JavaScriptCompiler.prototype = {
if (top instanceof Literal) {
// Literals do not need to be inlined
prefix = stack = top.value;
stack = [top.value];
prefix = ['(', stack];
usedLiteral = true;
} else {
// Get or create the current stack name for use by the inline
createdStack = true;
var name = this.incrStack();
prefix = '(' + this.push(name) + ' = ' + top + ')';
prefix = ['((', this.push(name), ' = ', top, ')'];
stack = this.topStack();
}
@@ -747,7 +789,7 @@ JavaScriptCompiler.prototype = {
if (createdStack) {
this.stackSlot--;
}
this.push('(' + prefix + item + ')');
this.push(prefix.concat(item, ')'));
},
incrStack: function() {
@@ -768,7 +810,7 @@ JavaScriptCompiler.prototype = {
this.compileStack.push(entry);
} else {
var stack = this.incrStack();
this.pushSource(stack + " = " + entry + ";");
this.pushSource([stack, ' = ', entry, ';']);
this.compileStack.push(stack);
}
}
@@ -816,25 +858,11 @@ JavaScriptCompiler.prototype = {
},
quotedString: function(str) {
return '"' + str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
return this.source.quotedString(str);
},
objectLiteral: function(obj) {
var pairs = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
pairs.push(this.quotedString(key) + ':' + obj[key]);
}
}
return '{' + pairs.join(',') + '}';
return this.source.objectLiteral(obj);
},
setupHelper: function(paramSize, name, blockHelper) {
@@ -846,7 +874,7 @@ JavaScriptCompiler.prototype = {
params: params,
paramsInit: paramsInit,
name: foundHelper,
callParams: [this.contextName(0)].concat(params).join(", ")
callParams: [this.contextName(0)].concat(params)
};
},
@@ -891,11 +919,11 @@ JavaScriptCompiler.prototype = {
}
if (this.trackIds) {
options.ids = "[" + ids.join(",") + "]";
options.ids = this.source.generateArray(ids);
}
if (this.stringParams) {
options.types = "[" + types.join(",") + "]";
options.contexts = "[" + contexts.join(",") + "]";
options.types = this.source.generateArray(types);
options.contexts = this.source.generateArray(contexts);
}
if (this.options.data) {
@@ -906,7 +934,7 @@ JavaScriptCompiler.prototype = {
if (useRegister) {
this.useRegister('options');
params.push('options');
return 'options=' + options;
return ['options=', options];
} else {
params.push(options);
return '';
@@ -914,6 +942,7 @@ JavaScriptCompiler.prototype = {
}
};
var reservedWords = (
"break else new var" +
" case finally return void" +
+57 -20
View File
@@ -2,6 +2,9 @@
var fs = require('fs'),
Handlebars = require('./index'),
basename = require('path').basename,
SourceMap = require('source-map'),
SourceMapConsumer = SourceMap.SourceMapConsumer,
SourceNode = SourceMap.SourceNode,
uglify = require('uglify-js');
module.exports.cli = function(opts) {
@@ -45,20 +48,23 @@ module.exports.cli = function(opts) {
var extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
extension = new RegExp('\\.' + extension + '$');
var output = [];
var output = new SourceNode();
if (!opts.simple) {
if (opts.amd) {
output.push('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
} else if (opts.commonjs) {
output.push('var Handlebars = require("' + opts.commonjs + '");');
output.add('var Handlebars = require("' + opts.commonjs + '");');
} else {
output.push('(function() {\n');
output.add('(function() {\n');
}
output.push(' var template = Handlebars.template, templates = ');
output.push(opts.namespace);
output.push(' = ');
output.push(opts.namespace);
output.push(' || {};\n');
output.add(' var template = Handlebars.template, templates = ');
if (opts.namespace) {
output.add(opts.namespace);
output.add(' = ');
output.add(opts.namespace);
output.add(' || ');
}
output.add('{};\n');
}
function processTemplate(template, root) {
var path = template,
@@ -83,6 +89,9 @@ module.exports.cli = function(opts) {
knownHelpersOnly: opts.o
};
if (opts.map) {
options.srcName = path;
}
if (opts.data) {
options.data = true;
}
@@ -95,18 +104,26 @@ module.exports.cli = function(opts) {
}
template = template.replace(extension, '');
var precompiled = Handlebars.precompile(data, options);
// If we are generating a source map, we have to reconstruct the SourceNode object
if (opts.map) {
var consumer = new SourceMapConsumer(precompiled.map);
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
}
if (opts.simple) {
output.push(Handlebars.precompile(data, options) + '\n');
output.add([precompiled, '\n']);
} else if (opts.partial) {
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
output.push('return ');
output.add('return ');
}
output.push('Handlebars.partials[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
output.add(['Handlebars.partials[\'', template, '\'] = template(', precompiled, ');\n']);
} else {
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
output.push('return ');
output.add('return ');
}
output.push('templates[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
output.add(['templates[\'', template, '\'] = template(', precompiled, ');\n']);
}
}
}
@@ -120,22 +137,42 @@ module.exports.cli = function(opts) {
if (opts.amd) {
if(opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
if(opts.partial){
output.push('return Handlebars.partials;\n');
output.add('return Handlebars.partials;\n');
} else {
output.push('return templates;\n');
output.add('return templates;\n');
}
}
output.push('});');
output.add('});');
} else if (!opts.commonjs) {
output.push('})();');
output.add('})();');
}
}
output = output.join('');
if (opts.map) {
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
}
output = output.toStringWithSourceMap();
output.map = output.map + '';
if (opts.min) {
output = uglify.minify(output, {fromString: true}).code;
output = uglify.minify(output.code, {
fromString: true,
outSourceMap: opts.map,
inSourceMap: JSON.parse(output.map)
});
if (opts.map) {
output.code += '\n//# sourceMappingURL=' + opts.map + '\n';
}
}
if (opts.map) {
fs.writeFileSync(opts.map, output.map, 'utf8');
}
output = output.code;
if (opts.output) {
fs.writeFileSync(opts.output, output, 'utf8');
} else {
+2 -1
View File
@@ -21,7 +21,8 @@
"node": ">=0.4.7"
},
"dependencies": {
"optimist": "~0.3"
"optimist": "~0.3",
"source-map": "^0.1.40"
},
"optionalDependencies": {
"uglify-js": "~2.3"
+2
View File
@@ -9,6 +9,8 @@ global.Handlebars = undefined;
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
global.CompilerContext = {
browser: true,
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
+1 -1
View File
@@ -1,6 +1,6 @@
define(['handlebars.runtime'], function(Handlebars) {
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
return templates['empty'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
return "";
return "";
},"useData":true});
});
+4
View File
@@ -60,6 +60,10 @@ describe('helpers', function() {
}};
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
});
it('helper returning undefined value', function() {
shouldCompileTo(' {{nothere}}', [{}, {nothere: function() {}}], ' ');
shouldCompileTo(' {{#nothere}}{{/nothere}}', [{}, {nothere: function() {}}], ' ');
});
it("block helper", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
+1 -1
View File
@@ -63,7 +63,7 @@ describe('javascript-compiler api', function() {
});
it('should allow append buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
return $superAppend.call(this, string + ' + "_foo"');
return $superAppend.call(this, [string, ' + "_foo"']);
};
shouldCompileTo("{{foo}}", { foo: "food" }, "food_foo");
});
+15 -1
View File
@@ -98,7 +98,8 @@ describe('precompiler', function() {
});
it('should output multiple amd', function() {
Handlebars.precompile = function() { return 'amd'; };
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars'});
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars', namespace: 'foo'});
equal(/templates = foo = foo \|\|/.test(log), true);
equal(/return templates/.test(log), true);
equal(/template\(amd\)/.test(log), true);
});
@@ -156,4 +157,17 @@ describe('precompiler', function() {
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, extension: 'handlebars'});
equal(log, 'min');
});
it('should output map', function() {
fs.writeFileSync = function(_file, _content) {
console.error(arguments);
file = _file;
content = _content;
};
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], map: 'foo.js.map', extension: 'handlebars'});
equal(file, 'foo.js.map');
equal(/sourceMappingURL=/.test(log), true);
});
});
+45
View File
@@ -0,0 +1,45 @@
/*global CompilerContext, Handlebars */
var SourceMap = require('source-map'),
SourceMapConsumer = SourceMap.SourceMapConsumer;
describe('source-map', function() {
if (!Handlebars.precompile) {
return;
}
it('should safely include source map info', function() {
var template = Handlebars.precompile('{{hello}}', {destName: 'dest.js', srcName: 'src.hbs'});
equal(!!template.code, true);
equal(!!template.map, !CompilerContext.browser);
});
it('should map source properly', function() {
var source = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
template = Handlebars.precompile(source, {destName: 'dest.js', srcName: 'src.hbs'});
if (template.map) {
var consumer = new SourceMapConsumer(template.map),
lines = template.code.split('\n'),
srcLines = source.split('\n'),
generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines);
var mapped = consumer.originalPositionFor(generated);
equal(mapped.line, source.line);
equal(mapped.column, source.column);
}
});
});
function grepLine(token, lines) {
for (var i = 0; i < lines.length; i++) {
var column = lines[i].indexOf(token);
if (column >= 0) {
return {
line: i+1,
column: column
};
}
}
}