1 | var through = require('through2');
|
2 | var Readable = require('readable-stream').Readable;
|
3 |
|
4 | var concat = require('concat-stream');
|
5 | var duplexer = require('duplexer2');
|
6 | var acorn = require('acorn-node');
|
7 | var walkAst = require('acorn-node/walk').full;
|
8 | var scan = require('scope-analyzer');
|
9 | var unparse = require('escodegen').generate;
|
10 | var inspect = require('object-inspect');
|
11 | var evaluate = require('static-eval');
|
12 | var copy = require('shallow-copy');
|
13 | var has = require('has');
|
14 | var MagicString = require('magic-string');
|
15 | var convertSourceMap = require('convert-source-map');
|
16 | var mergeSourceMap = require('merge-source-map');
|
17 |
|
18 | module.exports = function parse (modules, opts) {
|
19 | if (!opts) opts = {};
|
20 | var vars = opts.vars || {};
|
21 | var varModules = opts.varModules || {};
|
22 | var parserOpts = copy(opts.parserOpts || {});
|
23 | var updates = [];
|
24 | var moduleBindings = [];
|
25 | var sourcemapper;
|
26 | var inputMap;
|
27 |
|
28 | var output = through();
|
29 | var body, ast;
|
30 | return duplexer(concat({ encoding: 'buffer' }, function (buf) {
|
31 | try {
|
32 | body = buf.toString('utf8').replace(/^#!/, '//#!');
|
33 | var matches = false;
|
34 | for (var key in modules) {
|
35 | if (body.indexOf(key) !== -1) {
|
36 | matches = true;
|
37 | break;
|
38 | }
|
39 | }
|
40 |
|
41 | if (!matches) {
|
42 |
|
43 | output.end(buf);
|
44 | return;
|
45 | }
|
46 |
|
47 | if (opts.sourceMap) {
|
48 | inputMap = convertSourceMap.fromSource(body);
|
49 | if (inputMap) inputMap = inputMap.toObject();
|
50 | body = convertSourceMap.removeComments(body);
|
51 | sourcemapper = new MagicString(body);
|
52 | }
|
53 |
|
54 | ast = acorn.parse(body, parserOpts);
|
55 |
|
56 | scan.crawl(ast);
|
57 | walkAst(ast, walk);
|
58 | }
|
59 | catch (err) { return error(err) }
|
60 |
|
61 | finish(body);
|
62 | }), output);
|
63 |
|
64 | function finish (src) {
|
65 | var pos = 0;
|
66 | src = String(src);
|
67 |
|
68 | moduleBindings.forEach(function (binding) {
|
69 | if (binding.isReferenced()) {
|
70 | return;
|
71 | }
|
72 | var node = binding.initializer;
|
73 | if (node.type === 'VariableDeclarator') {
|
74 | var i = node.parent.declarations.indexOf(node);
|
75 | if (node.parent.declarations.length === 1) {
|
76 |
|
77 | updates.push({
|
78 | start: node.parent.start,
|
79 | offset: node.parent.end - node.parent.start,
|
80 | stream: st()
|
81 | });
|
82 | } else if (i === node.parent.declarations.length - 1) {
|
83 | updates.push({
|
84 |
|
85 | start: node.parent.declarations[i - 1].end,
|
86 | offset: node.end - node.parent.declarations[i - 1].end,
|
87 | stream: st()
|
88 | });
|
89 | } else {
|
90 | updates.push({
|
91 |
|
92 | start: node.start,
|
93 | offset: node.parent.declarations[i + 1].start - node.start,
|
94 | stream: st()
|
95 | });
|
96 | }
|
97 | } else if (node.parent.type === 'SequenceExpression' && node.parent.expressions.length > 1) {
|
98 | var i = node.parent.expressions.indexOf(node);
|
99 | if (i === node.parent.expressions.length - 1) {
|
100 | updates.push({
|
101 |
|
102 | start: node.parent.expressions[i - 1].end,
|
103 | offset: node.end - node.parent.expressions[i - 1].end,
|
104 | stream: st()
|
105 | });
|
106 | } else {
|
107 | updates.push({
|
108 |
|
109 | start: node.start,
|
110 | offset: node.parent.expressions[i + 1].start - node.start,
|
111 | stream: st()
|
112 | });
|
113 | }
|
114 | } else {
|
115 | if (node.parent.type === 'ExpressionStatement') node = node.parent;
|
116 | updates.push({
|
117 | start: node.start,
|
118 | offset: node.end - node.start,
|
119 | stream: st()
|
120 | });
|
121 | }
|
122 | });
|
123 | updates.sort(function(a, b) { return a.start - b.start; });
|
124 |
|
125 | (function next () {
|
126 | if (updates.length === 0) return done();
|
127 | var s = updates.shift();
|
128 |
|
129 | output.write(src.slice(pos, s.start));
|
130 | pos = s.start + s.offset;
|
131 |
|
132 | s.stream.pipe(output, { end: false });
|
133 | if (opts.sourceMap) {
|
134 | s.stream.pipe(concat({ encoding: 'string' }, function (chunk) {
|
135 |
|
136 |
|
137 | if (s.offset === 0) {
|
138 | sourcemapper.appendRight(s.start, chunk);
|
139 | } else {
|
140 | sourcemapper.overwrite(s.start, s.start + s.offset, chunk);
|
141 | }
|
142 | })).on('finish', next);
|
143 | } else {
|
144 | s.stream.on('end', next);
|
145 | }
|
146 | })();
|
147 |
|
148 | function done () {
|
149 | output.write(src.slice(pos));
|
150 | if (opts.sourceMap) {
|
151 | var map = sourcemapper.generateMap({
|
152 | source: opts.inputFilename || 'input.js',
|
153 | includeContent: true
|
154 | });
|
155 | if (inputMap) {
|
156 | var merged = mergeSourceMap(inputMap, map);
|
157 | output.write('\n' + convertSourceMap.fromObject(merged).toComment() + '\n');
|
158 | } else {
|
159 | output.write('\n//# sourceMappingURL=' + map.toUrl() + '\n');
|
160 | }
|
161 | }
|
162 | output.end();
|
163 | }
|
164 | }
|
165 |
|
166 | function error (msg) {
|
167 | var err = typeof msg === 'string' ? new Error(msg) : msg;
|
168 | output.emit('error', err);
|
169 | }
|
170 |
|
171 | function walk (node) {
|
172 | if (opts.sourceMap) {
|
173 | sourcemapper.addSourcemapLocation(node.start);
|
174 | sourcemapper.addSourcemapLocation(node.end);
|
175 | }
|
176 |
|
177 | var isreq = isRequire(node);
|
178 | var isreqm = false, isreqv = false, reqid;
|
179 | if (isreq) {
|
180 | reqid = node.arguments[0].value;
|
181 | isreqm = has(modules, reqid);
|
182 | isreqv = has(varModules, reqid);
|
183 | }
|
184 |
|
185 | if (isreqv && node.parent.type === 'VariableDeclarator'
|
186 | && node.parent.id.type === 'Identifier') {
|
187 | var binding = scan.getBinding(node.parent.id);
|
188 | if (binding) binding.value = varModules[reqid];
|
189 | }
|
190 | else if (isreqv && node.parent.type === 'AssignmentExpression'
|
191 | && node.parent.left.type === 'Identifier') {
|
192 | var binding = scan.getBinding(node.parent.left);
|
193 | if (binding) binding.value = varModules[reqid];
|
194 | }
|
195 | else if (isreqv && node.parent.type === 'MemberExpression'
|
196 | && isStaticProperty(node.parent.property)
|
197 | && node.parent.parent.type === 'VariableDeclarator'
|
198 | && node.parent.parent.id.type === 'Identifier') {
|
199 | var binding = scan.getBinding(node.parent.parent.id);
|
200 | var v = varModules[reqid][resolveProperty(node.parent.property)];
|
201 | if (binding) binding.value = v;
|
202 | }
|
203 | else if (isreqv && node.parent.type === 'MemberExpression'
|
204 | && node.parent.property.type === 'Identifier') {
|
205 |
|
206 | }
|
207 | else if (isreqv && node.parent.type === 'CallExpression') {
|
208 |
|
209 | }
|
210 |
|
211 | if (isreqm && node.parent.type === 'VariableDeclarator'
|
212 | && node.parent.id.type === 'Identifier') {
|
213 | var binding = scan.getBinding(node.parent.id);
|
214 | if (binding) {
|
215 | binding.module = modules[reqid];
|
216 | binding.initializer = node.parent;
|
217 | binding.remove(node.parent.id);
|
218 | moduleBindings.push(binding);
|
219 | }
|
220 | }
|
221 | else if (isreqm && node.parent.type === 'AssignmentExpression'
|
222 | && node.parent.left.type === 'Identifier') {
|
223 | var binding = scan.getBinding(node.parent.left);
|
224 | if (binding) {
|
225 | binding.module = modules[reqid];
|
226 | binding.initializer = node.parent;
|
227 | binding.remove(node.parent.left);
|
228 | moduleBindings.push(binding);
|
229 | }
|
230 | }
|
231 | else if (isreqm && node.parent.type === 'MemberExpression'
|
232 | && isStaticProperty(node.parent.property)
|
233 | && node.parent.parent.type === 'VariableDeclarator'
|
234 | && node.parent.parent.id.type === 'Identifier') {
|
235 | var binding = scan.getBinding(node.parent.parent.id);
|
236 | if (binding) {
|
237 | binding.module = modules[reqid][resolveProperty(node.parent.property)];
|
238 | binding.initializer = node.parent.parent;
|
239 | binding.remove(node.parent.parent.id);
|
240 | moduleBindings.push(binding);
|
241 | }
|
242 | }
|
243 | else if (isreqm && node.parent.type === 'MemberExpression'
|
244 | && isStaticProperty(node.parent.property)) {
|
245 | var name = resolveProperty(node.parent.property);
|
246 | var cur = copy(node.parent.parent);
|
247 | cur.callee = copy(node.parent.property);
|
248 | cur.callee.parent = cur;
|
249 | traverse(cur.callee, modules[reqid][name]);
|
250 | }
|
251 | else if (isreqm && node.parent.type === 'CallExpression') {
|
252 | var cur = copy(node.parent);
|
253 | var iname = Math.pow(16,8) * Math.random();
|
254 | cur.callee = {
|
255 | type: 'Identifier',
|
256 | name: '_' + Math.floor(iname).toString(16),
|
257 | parent: cur
|
258 | };
|
259 | traverse(cur.callee, modules[reqid]);
|
260 | }
|
261 |
|
262 | if (node.type === 'Identifier') {
|
263 | var binding = scan.getBinding(node)
|
264 | if (binding && binding.module) traverse(node, binding.module, binding);
|
265 | }
|
266 | }
|
267 |
|
268 | function traverse (node, val, binding) {
|
269 | for (var p = node; p; p = p.parent) {
|
270 | if (p.start === undefined || p.end === undefined) continue;
|
271 | }
|
272 |
|
273 | if (node.parent.type === 'CallExpression') {
|
274 | if (typeof val !== 'function') {
|
275 | return error(
|
276 | 'tried to statically call ' + inspect(val)
|
277 | + ' as a function'
|
278 | );
|
279 | }
|
280 |
|
281 | var xvars = getVars(node.parent, vars);
|
282 | xvars[node.name] = val;
|
283 |
|
284 | var res = evaluate(node.parent, xvars);
|
285 | if (res !== undefined) {
|
286 | if (binding) binding.remove(node)
|
287 | updates.push({
|
288 | start: node.parent.start,
|
289 | offset: node.parent.end - node.parent.start,
|
290 | stream: isStream(res) ? wrapStream(res) : st(String(res))
|
291 | });
|
292 | }
|
293 | }
|
294 | else if (node.parent.type === 'MemberExpression') {
|
295 | if (!isStaticProperty(node.parent.property)) {
|
296 | return error(
|
297 | 'dynamic property in member expression: '
|
298 | + body.slice(node.parent.start, node.parent.end)
|
299 | );
|
300 | }
|
301 |
|
302 | var cur = node.parent.parent;
|
303 |
|
304 | if (cur.type === 'MemberExpression') {
|
305 | cur = cur.parent;
|
306 | if (cur.type !== 'CallExpression'
|
307 | && cur.parent.type === 'CallExpression') {
|
308 | cur = cur.parent;
|
309 | }
|
310 | }
|
311 | if (node.parent.type === 'MemberExpression'
|
312 | && (cur.type !== 'CallExpression'
|
313 | && cur.type !== 'MemberExpression')) {
|
314 | cur = node.parent;
|
315 | }
|
316 |
|
317 | var xvars = getVars(cur, vars);
|
318 | xvars[node.name] = val;
|
319 |
|
320 | var res = evaluate(cur, xvars);
|
321 | if (res === undefined && cur.type === 'CallExpression') {
|
322 |
|
323 | var callee = evaluate(cur.callee, xvars);
|
324 | var args = cur.arguments.map(function (arg) {
|
325 |
|
326 |
|
327 | if (arg.type === 'FunctionExpression' || arg.type === 'ArrowFunctionExpression') {
|
328 | var fn = function () {
|
329 | throw new Error('static-module: cannot call callbacks defined inside source code');
|
330 | };
|
331 | fn.toString = function () {
|
332 | return body.slice(arg.start, arg.end);
|
333 | };
|
334 | return fn;
|
335 | }
|
336 | return evaluate(arg, xvars);
|
337 | });
|
338 |
|
339 | if (callee !== undefined) {
|
340 | try {
|
341 | res = callee.apply(null, args);
|
342 | } catch (err) {
|
343 |
|
344 | }
|
345 | }
|
346 | }
|
347 |
|
348 | if (res !== undefined) {
|
349 | if (binding) binding.remove(node)
|
350 | updates.push({
|
351 | start: cur.start,
|
352 | offset: cur.end - cur.start,
|
353 | stream: isStream(res) ? wrapStream(res) : st(String(res))
|
354 | });
|
355 | }
|
356 | }
|
357 | else if (node.parent.type === 'UnaryExpression') {
|
358 | var xvars = getVars(node.parent, vars);
|
359 | xvars[node.name] = val;
|
360 |
|
361 | var res = evaluate(node.parent, xvars);
|
362 | if (res !== undefined) {
|
363 | if (binding) binding.remove(node)
|
364 | updates.push({
|
365 | start: node.parent.start,
|
366 | offset: node.parent.end - node.parent.start,
|
367 | stream: isStream(res) ? wrapStream(res) : st(String(res))
|
368 | });
|
369 | } else {
|
370 | output.emit('error', new Error(
|
371 | 'unsupported unary operator: ' + node.parent.operator
|
372 | ));
|
373 | }
|
374 | }
|
375 | else {
|
376 | output.emit('error', new Error(
|
377 | 'unsupported type for static module: ' + node.parent.type
|
378 | + '\nat expression:\n\n ' + unparse(node.parent) + '\n'
|
379 | ));
|
380 | }
|
381 | }
|
382 | }
|
383 |
|
384 | function isRequire (node) {
|
385 | var c = node.callee;
|
386 | return c
|
387 | && node.type === 'CallExpression'
|
388 | && c.type === 'Identifier'
|
389 | && c.name === 'require'
|
390 | ;
|
391 | }
|
392 |
|
393 | function isStream (s) {
|
394 | return s && typeof s === 'object' && typeof s.pipe === 'function';
|
395 | }
|
396 |
|
397 | function wrapStream (s) {
|
398 | if (typeof s.read === 'function') return s
|
399 | else return (new Readable).wrap(s)
|
400 | }
|
401 |
|
402 | function isStaticProperty(node) {
|
403 | return node.type === 'Identifier' || node.type === 'Literal';
|
404 | }
|
405 |
|
406 | function resolveProperty(node) {
|
407 | return node.type === 'Identifier' ? node.name : node.value;
|
408 | }
|
409 |
|
410 | function st (msg) {
|
411 | var r = new Readable;
|
412 | r._read = function () {};
|
413 | if (msg != null) r.push(msg);
|
414 | r.push(null);
|
415 | return r;
|
416 | }
|
417 |
|
418 | function nearestScope(node) {
|
419 | do {
|
420 | var scope = scan.scope(node);
|
421 | if (scope) return scope;
|
422 | } while ((node = node.parent));
|
423 | }
|
424 |
|
425 | function getVars(node, vars) {
|
426 | var xvars = copy(vars || {});
|
427 | var scope = nearestScope(node);
|
428 | if (scope) {
|
429 | scope.forEachAvailable(function (binding, name) {
|
430 | if (binding.hasOwnProperty('value')) xvars[name] = binding.value;
|
431 | });
|
432 | }
|
433 | return xvars;
|
434 | }
|