1
|
|
|
import fs from 'fs'; |
2
|
|
|
import { all, promisify, reject, resolve } from 'bluebird'; |
3
|
|
|
import { |
4
|
|
|
T, |
5
|
|
|
cond, |
6
|
|
|
curry, |
7
|
|
|
endsWith, |
8
|
|
|
fromPairs, |
9
|
|
|
map, |
10
|
|
|
merge, |
11
|
|
|
path, |
12
|
|
|
startsWith, |
13
|
|
|
uniq |
14
|
|
|
} from 'ramda'; |
15
|
|
|
import { transform } from 'babel-core'; |
16
|
|
|
import { compileES6 } from './compiler'; |
17
|
|
|
|
18
|
|
|
const readFile = promisify(fs.readFile); |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Ensures a piece of source has no import or require declarations |
22
|
|
|
* |
23
|
|
|
* @param {String} source |
24
|
|
|
* @return {Promise} |
25
|
|
|
*/ |
26
|
|
|
export const ensureNoImports = curry((filename, source) => { |
27
|
|
|
const { modules } = inspect(source); |
28
|
|
|
return modules.length |
29
|
|
|
? reject(new Error(`Cannot import modules on autocomplete files (${filename})`)) |
30
|
|
|
: resolve(); |
31
|
|
|
}); |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Traverses the AST, getting the imported modules and compile to pairs |
35
|
|
|
* |
36
|
|
|
* @author Marcelo Haskel Camargo |
37
|
|
|
* @param {String} source |
38
|
|
|
* @return {Promise<String[][]>} |
39
|
|
|
*/ |
40
|
|
|
export function compileModulesFromSource(source) { |
41
|
|
|
const { modules } = inspect(source); |
42
|
|
|
return all(modules.filter(startsWith('./')).map(compileModule)); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Compiles a module to a pair with (filename :: string, source :: string) |
47
|
|
|
* |
48
|
|
|
* @author Marcelo Haskell Camargo |
49
|
|
|
* @param {String} module |
50
|
|
|
* @return {Promise} |
51
|
|
|
*/ |
52
|
|
|
export function compileModule(module) { |
53
|
|
|
const extensionIs = extension => ~endsWith(extension, module); |
54
|
|
|
|
55
|
|
|
return readFile(module, 'utf-8') |
56
|
|
|
.then(cond([ |
57
|
|
|
[extensionIs('.js'), compileES6], |
58
|
|
|
[extensionIs('.json'), JSON.parse & JSON.stringify], |
59
|
|
|
[T, ~reject(new Error(`Unknown module loader for file ${module}`))]])) |
60
|
|
|
.then(source => [module, source]); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Evaluates a list of pairs of modules. modules :: [(String, String)] |
65
|
|
|
* |
66
|
|
|
* @author Marcelo Haskell Camargo |
67
|
|
|
* @param {NodeVM} vm - Virtual machine instance to run |
|
|
|
|
68
|
|
|
* @param {String[][]} modules pairs, with (name :: string, source :: string) |
|
|
|
|
69
|
|
|
*/ |
70
|
|
|
export const evaluateModules = (vm, modules) => fromPairs(map(([module, source]) => { |
71
|
|
|
// JSON doesn't need to run on VM. We can directly parse it |
72
|
|
|
const convertToBytecode = cond([ |
73
|
|
|
[endsWith('.json'), ~JSON.parse(source)], |
74
|
|
|
[endsWith('.js'), vm.run(source, _)], |
|
|
|
|
75
|
|
|
[T, module => { |
76
|
|
|
throw new Error(`Unknown file type for ${module}`); |
77
|
|
|
}] |
78
|
|
|
]); |
79
|
|
|
|
80
|
|
|
return [module, convertToBytecode(module)]; |
81
|
|
|
}, modules)); |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Inspects a JS source and returns processed information, such as ES5 code, |
85
|
|
|
* the ast, source map and the used modules |
86
|
|
|
* |
87
|
|
|
* @author Marcelo Haskell Camargo |
88
|
|
|
* @param {String} source - ES6 source |
89
|
|
|
* @return {String[]} |
90
|
|
|
*/ |
91
|
|
|
export function inspect(source) { |
92
|
|
|
const modules = []; |
93
|
|
|
const result = transform(source, { |
94
|
|
|
comments: false, |
95
|
|
|
compact: true, |
96
|
|
|
presets: ['es2015', 'react'], |
97
|
|
|
plugins: [ |
98
|
|
|
['transform-react-jsx', { pragma: '__render__' }], |
99
|
|
|
[~({ |
100
|
|
|
visitor: { |
101
|
|
|
ImportDeclaration({ node }) { |
102
|
|
|
modules.push(node.source.value); |
103
|
|
|
}, |
104
|
|
|
CallExpression({ node }) { |
105
|
|
|
// Find and extract require(module) |
106
|
|
|
const callee = path(['callee', 'name'], node); |
107
|
|
|
|
108
|
|
|
if (callee === 'require') { |
109
|
|
|
const [moduleNode] = node.arguments; |
110
|
|
|
const isLiteralModule = moduleNode && moduleNode.type === 'StringLiteral'; |
111
|
|
|
|
112
|
|
|
if (isLiteralModule) { |
113
|
|
|
modules.push(moduleNode.value); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
})] |
119
|
|
|
] |
120
|
|
|
}); |
121
|
|
|
|
122
|
|
|
return merge(result, { modules: uniq(modules) }); |
123
|
|
|
} |
124
|
|
|
|