Skip to content
This repository was archived by the owner on Sep 25, 2018. It is now read-only.

Commit 3601b12

Browse files
committed
Carve out a code path to defer compilation of generated code...
So that we can generate a big module string which can be generated at build-time and saved to disk. This is important for two reasons: 1. This allows the browser to avoid using scion compiler at runtime. Compiled SCXML-to-js modules are much smaller runtime dependency over the wire than the SCION compiler. 2. Node.js --inspect does not work for code evaluated at runtime using eval or node vm module. See issue nodejs/node#8369. So to use chrome dev tools and source maps to debug SCXML, it is currently necessary to use ahead-of-time compilation to generate a module file as a workaround.
1 parent 6186305 commit 3601b12

File tree

7 files changed

+297
-902
lines changed

7 files changed

+297
-902
lines changed

dist/scxml.js

Lines changed: 171 additions & 780 deletions
Large diffs are not rendered by default.

lib/compiler/scjson-to-module.js

Lines changed: 48 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -29,47 +29,52 @@ function stripAttrNsPrefix(attrName){
2929
return m[1];
3030
}
3131

32+
function parseJsCode(fnBody, action, docUrl, node, isExpression){
33+
//console.log(fnBody, docUrl);
34+
var tokens = esprima.tokenize(fnBody, { loc: true });
35+
var lastTokenEndLine = 1,
36+
lastTokenEndCol = 0;
37+
tokens.forEach(function(token, i){
38+
var numLinesPadding = token.loc.start.line - lastTokenEndLine,
39+
numColsPadding = token.loc.start.column - lastTokenEndCol;
40+
var whitespace = (function(){
41+
var s = [];
42+
for(var i = 0; i < numLinesPadding; i++){
43+
s.push('\n');
44+
}
45+
for(var j = 0; j < numColsPadding ; j++){
46+
s.push(' ');
47+
}
48+
return s.join('');
49+
})();
50+
if(!(isExpression && i==0)){
51+
//skip whitespace
52+
node.add(whitespace);
53+
}
54+
var tokenNode = new SourceNode(
55+
action.$line + token.loc.start.line,
56+
token.loc.start.line === 1 ? action.$column + token.loc.start.column : token.loc.start.column,
57+
docUrl,
58+
token.value
59+
);
60+
//console.log('tokenNode',tokenNode);
61+
node.add(tokenNode);
62+
lastTokenEndLine = token.loc.end.line;
63+
lastTokenEndCol = token.loc.end.column;
64+
});
65+
}
66+
3267
function generateFnDeclaration(fnName,fnBody,action,docUrl,isExpression,parseGeneratedCodeForSourceMap){
3368
if(printTrace) console.log('generateFnDeclaration',fnName,fnBody);
3469

35-
var tokens = esprima.tokenize(fnBody, { loc: true });
36-
var lastTokenEndLine = 1,
37-
lastTokenEndCol = 0;
3870
var node = new SourceNode();
3971
node.add('function ' + fnName + FN_ARGS + '{\n');
4072
if(isExpression){
4173
var returnNode = new SourceNode(action.$line + 1, action.$column, docUrl, 'return ');
4274
node.add(returnNode);
4375
}
4476
if(parseGeneratedCodeForSourceMap){
45-
tokens.forEach(function(token, i){
46-
var numLinesPadding = token.loc.start.line - lastTokenEndLine,
47-
numColsPadding = token.loc.start.column - lastTokenEndCol;
48-
var whitespace = (function(){
49-
var s = [];
50-
for(var i = 0; i < numLinesPadding; i++){
51-
s.push('\n');
52-
}
53-
for(var j = 0; j < numColsPadding ; j++){
54-
s.push(' ');
55-
}
56-
return s.join('');
57-
})();
58-
if(!(isExpression && i==0)){
59-
//skip whitespace
60-
node.add(whitespace);
61-
}
62-
var tokenNode = new SourceNode(
63-
action.$line + token.loc.start.line,
64-
token.loc.start.line === 1 ? action.$column + token.loc.start.column : token.loc.start.column,
65-
docUrl,
66-
token.value
67-
);
68-
//console.log('tokenNode',tokenNode);
69-
node.add(tokenNode);
70-
lastTokenEndLine = token.loc.end.line;
71-
lastTokenEndCol = token.loc.end.column;
72-
});
77+
parseJsCode(fnBody, action, docUrl, node, isExpression);
7378
}else{
7479
node.add(
7580
new SourceNode(
@@ -194,16 +199,17 @@ function dumpFunctionDeclarations(fnDecAccumulator){
194199

195200
function dumpHeader(strict){
196201
var d = new Date();
197-
var strictStr = (strict ? "'use strict';\n" : "");
198-
return strictStr + '//Generated on ' + d.toLocaleDateString() + ' ' + d.toLocaleTimeString() + ' by the SCION SCXML compiler';
202+
return '//Generated on ' + d.toLocaleDateString() + ' ' + d.toLocaleTimeString() + ' by the SCION SCXML compiler\n' +
203+
(strict ? "'use strict';\n" : "");
199204
}
200205

201206
function generateFactoryFunctionWrapper(o, name, options){
202207

203208
var root = new SourceNode(null,null,null,[
204-
o.headerString + '\n' ,
209+
//o.headerString + '\n' ,
205210
'(function (_x,_sessionid,_ioprocessors,In){\n' ,
206211
' var _name = \'' + name + '\';\n',
212+
new SourceNode(null,null,null,""), //create a dummy node at index 2, which we might use to inject datamodel and scripts
207213
o.sendString,
208214
o.sendIdLocationString,
209215
o.earlyBindingFnDeclaration,
@@ -218,11 +224,7 @@ function generateFactoryFunctionWrapper(o, name, options){
218224
])
219225
);
220226

221-
var s = root.toStringWithSourceMap();
222-
223-
return s.code + '\n' +
224-
'//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
225-
new Buffer(s.map.toString()).toString('base64');
227+
return root;
226228
}
227229

228230
ModuleBuilder.prototype.generateModule = function(){
@@ -420,7 +422,7 @@ ModuleBuilder.prototype.handleExternalActionScript = function(action) {
420422
if (!this.externalActionScripts.has(action.src)) {
421423
this.externalActionScripts.add(action.src);
422424
action.$wrap = function(body) {
423-
return generateFnDeclaration(fnName, body, action);
425+
return generateFnDeclaration(fnName,body,{$line : 0, $column:0},action.src,false,true);
424426
}
425427

426428
this.rootState.rootScripts.push(action);
@@ -652,6 +654,7 @@ var actionTags = {
652654
var shallowArrayName = getVariableNameForShallowCopy(builder);
653655

654656
var forEachContents =
657+
(!action.index ? 'var ' + index + ';\n' : '') +
655658
'var ' + shallowArrayName + ' = ' + arr + ';\n'+
656659
'if(Array.isArray(' + shallowArrayName + ')){\n' +
657660
' for(' + index + ' = 0; ' + index + ' < ' + shallowArrayName + '.length;' + index + '++){\n' +
@@ -712,54 +715,8 @@ function generateIdlocationGenerator(sendIdAccumulator){
712715
'}';
713716
}
714717

715-
716-
module.exports = startTraversal;
717-
718-
//for executing directly under node.js
719-
if(require.main === module){
720-
//TODO: clean up command-line interface so that we do not expose unnecessary cruft
721-
var usage = 'Usage: $0 [ FILE | - ]';
722-
var argv = require('optimist').
723-
demand('d').
724-
alias('d', 'doc-url').
725-
usage(usage).
726-
argv;
727-
var input = argv._[0];
728-
var docUrl = argv.d;
729-
730-
if(!input){
731-
console.error(usage);
732-
process.exit(1);
733-
} else if(input === '-'){
734-
735-
//read from stdin or file
736-
process.stdin.setEncoding('utf8');
737-
process.stdin.resume();
738-
739-
var jsonString = '';
740-
process.stdin.on('data',function(s){
741-
jsonString += s;
742-
});
743-
744-
process.stdin.on('end',go.bind(this,docUrl));
745-
} else{
746-
var fs = require('fs');
747-
jsonString = fs.readFileSync(input,'utf8');
748-
go(docUrl);
749-
}
750-
}
751-
752-
function go(docUrl){
753-
if (!docUrl) {
754-
docUrl = '';
755-
}
756-
757-
startTraversal(docUrl, JSON.parse(jsonString), {debug: true}).then(
758-
function resolved(jsModule) {
759-
console.log(jsModule.module);
760-
},
761-
function rejected(err) {
762-
console.error(err);
763-
}
764-
);
765-
}
718+
module.exports = {
719+
startTraversal : startTraversal,
720+
parseJsCode : parseJsCode,
721+
dumpHeader : dumpHeader
722+
};

lib/compiler/scxml-to-scjson.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,3 @@ function transform(xmlString){
332332
}
333333

334334
module.exports = transform;
335-
336-
//for executing diretly under node.js
337-
if(require.main === module){
338-
//TODO: allow reading from stdin directly
339-
//TODO: use saxjs's support for streaming API.
340-
console.log(JSON.stringify(transform(require('fs').readFileSync(process.argv[2],'utf8')),4,4));
341-
}

lib/runtime/document-string-to-model.js

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var compilerInternals = require('./compiler-internals'),
2121
platform = require('./platform-bootstrap/node/platform'),
2222
fs = require('fs'),
2323
vm = require('vm'),
24+
SourceNode = require('source-map').SourceNode,
2425
assert = require('assert');
2526

2627
var scxmlToScjson = compilerInternals.scxmlToScjson,
@@ -55,12 +56,15 @@ function documentStringToModel(url,docString,cb,hostContext){
5556
function fetchScript(scriptInfo, hostContext) {
5657
return new Promise(function(resolve, reject) {
5758

58-
platform.getScriptFromUrl(scriptInfo.src,
59+
platform.getScriptFromUrl(scriptInfo,
5960
function(err, compiledScript, mimeType) {
6061
if (err) {
6162
reject(err);
6263
} else {
63-
scriptInfo.compiled = compiledScript;
64+
if(!hostContext.deferCompilation){
65+
var content = scriptInfo.$wrap ? scriptInfo.$wrap(scriptInfo.content) : scriptInfo.content;
66+
scriptInfo.compiled = new vm.Script(content, {filename: scriptInfo.src});
67+
}
6468
resolve(scriptInfo);
6569
}
6670
},
@@ -199,16 +203,49 @@ SCModel.prototype.prepare = function(cb, executionContext, hostContext) {
199203
const self = this;
200204
Promise.all(scriptPromises).then(function resolved(scripts) {
201205
try {
202-
if (self.datamodel) {
203-
self.datamodel.runInContext(executionContext);
204-
}
206+
if(hostContext.deferCompilation){
207+
var rootNode = new SourceNode(null, null, null);
205208

206-
for (let i = 0; i < scriptCount; i++) {
207-
self.rootScripts[i].compiled.runInContext(executionContext);
208-
}
209+
rootNode.add(scjsonToModule.dumpHeader(true));
210+
211+
//rootNode.add('module.exports = ');
212+
var injectionNode = self.module.children[2];
213+
214+
if(self.datamodel) {
215+
injectionNode.add(self.datamodel);
216+
injectionNode.add('\n');
217+
}
218+
219+
self.rootScripts.forEach(function(rootScript){
220+
if(rootScript.$wrap){
221+
injectionNode.add(rootScript.$wrap(rootScript.content));
222+
}else{
223+
scjsonToModule.parseJsCode(rootScript.content, {$line : 0, $column : 0}, rootScript.src, injectionNode, false)
224+
}
225+
injectionNode.add('\n');
226+
})
227+
228+
rootNode.add(self.module);
229+
230+
var s = rootNode.toStringWithSourceMap();
231+
var generatedCode = s.code + '\n' +
232+
'//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
233+
new Buffer(s.map.toString()).toString('base64')
234+
//console.log(generatedCode);
235+
var fn = vm.runInContext(generatedCode, executionContext);
236+
cb(null,fn);
237+
} else {
238+
if (self.datamodel) {
239+
self.datamodel.runInContext(executionContext);
240+
}
241+
242+
for (let i = 0; i < scriptCount; i++) {
243+
self.rootScripts[i].compiled.runInContext(executionContext);
244+
}
209245

210-
let modelFn = self.module.runInContext(executionContext);
211-
cb(undefined, modelFn);
246+
let modelFn = self.module.runInContext(executionContext);
247+
cb(undefined, modelFn);
248+
}
212249
} catch (e) {
213250
cb(e);
214251
}
@@ -225,12 +262,21 @@ function createModule(url,scJson,hostContext,cb){
225262
}
226263
}
227264

228-
scjsonToModule(url, scJson, hostContext).then(function resolved(rawModule) {
265+
scjsonToModule.startTraversal(url, scJson, hostContext).then(function resolved(rawModule) {
229266
if (platform.debug && rawModule.name) {
230267
fs.writeFileSync('/var/tmp/' + rawModule.name + '.scion', rawModule.module);
231268
}
232269

233-
compileModule(url, rawModule, hostContext, cb);
270+
if(hostContext.deferCompilation){
271+
cb(null, new SCModel(
272+
rawModule.name,
273+
rawModule.datamodel,
274+
rawModule.rootScripts,
275+
rawModule.module
276+
));
277+
} else {
278+
compileModule(url, rawModule, hostContext, cb);
279+
}
234280
}, function rejected(err) {
235281
cb(err);
236282
});

lib/runtime/facade.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,16 @@ module.exports = {
8484
},
8585
scion : require('scion-core')
8686
};
87+
88+
89+
if(require.main === module){
90+
var file = process.argv[2];
91+
var options = {deferCompilation : true};
92+
pathToModel(file, function(err, model){
93+
if(err) throw err;
94+
model.prepare(function(err, fnModel){
95+
if(err) throw err;
96+
console.log(fnModel.toString());
97+
}, options);
98+
}, options);
99+
}

lib/runtime/platform-bootstrap/node/platform.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,16 @@ module.exports = {
4040

4141
getResourceFromUrl : get.getResource,
4242

43-
getScriptFromUrl: function(url, cb, context, scriptInfo) {
44-
get.getResource(url, function(err, content) {
43+
getScriptFromUrl: function(script, cb, context, scriptInfo) {
44+
get.getResource(script.src, function(err, content) {
4545
if (err) {
4646
cb(err);
4747
return;
4848
}
4949

50-
if (typeof scriptInfo.$wrap === 'function') {
51-
content = scriptInfo.$wrap(content);
52-
}
53-
54-
var options = Object.assign({filename: url}, scriptInfo);
5550
try {
56-
var script = new vm.Script(content, options);
57-
cb(undefined, script);
51+
script.content = content;
52+
cb(undefined, content);
5853
} catch (e) {
5954
cb(e);
6055
}

test/node-test-server.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ http.createServer(function (req, res) {
3838
try {
3939
model.prepare(function(err, fnModel) {
4040
if (err) {
41-
console.error('model preparation error: ' + err);
41+
console.error('model preparation error: ' + err, err.stack);
4242
res.writeHead(500, {'Content-Type': 'text/plain'});
4343
res.end(err.message);
4444
return;
@@ -60,15 +60,15 @@ http.createServer(function (req, res) {
6060

6161
// TODO: timeout should be kicked off before fetch/compilation/preparation
6262
timeouts[sessionToken] = setTimeout(function(){cleanUp(sessionToken);},timeoutMs);
63-
});
63+
}, {deferCompilation : true});
6464
} catch(e) {
6565
console.log(e.stack);
6666
console.log(e);
6767
res.writeHead(500, {'Content-Type': 'text/plain'});
6868
res.end(e.message);
6969
}
7070
}
71-
});
71+
}, null, {deferCompilation : true});
7272

7373
}else if(reqJson.event && (typeof reqJson.sessionToken === "number")){
7474
console.log("sending event to statechart",reqJson.event);

0 commit comments

Comments
 (0)