UNPKG

16 kBJavaScriptView Raw
1var through = require('through2');
2var Readable = require('readable-stream').Readable;
3
4var concat = require('concat-stream');
5var duplexer = require('duplexer2');
6var acorn = require('acorn-node');
7var walkAst = require('acorn-node/walk').full;
8var scan = require('scope-analyzer');
9var unparse = require('escodegen').generate;
10var inspect = require('object-inspect');
11var evaluate = require('static-eval');
12var copy = require('shallow-copy');
13var has = require('has');
14var MagicString = require('magic-string');
15var convertSourceMap = require('convert-source-map');
16var mergeSourceMap = require('merge-source-map');
17
18module.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 // just pass it through
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 // scan.crawl does .parent tracking, so we can use acorn's builtin walker.
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 // remove the entire declaration
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 // remove ", a = 1"
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 // remove "a = 1, "
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 // remove ", a = 1"
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 // remove "a = 1, "
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 // We have to give magic-string the replacement string,
136 // so it can calculate the amount of lines and columns.
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 //vars[node.parent.parent.id.name] = varModules[reqid];
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 // static-eval can't safely evaluate code with callbacks, so do it manually in a safe way
323 var callee = evaluate(cur.callee, xvars);
324 var args = cur.arguments.map(function (arg) {
325 // Return a function stub for callbacks so that `static-module` users
326 // can do `callback.toString()` and get the original source
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 // Evaluate to undefined
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
384function 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
393function isStream (s) {
394 return s && typeof s === 'object' && typeof s.pipe === 'function';
395}
396
397function wrapStream (s) {
398 if (typeof s.read === 'function') return s
399 else return (new Readable).wrap(s)
400}
401
402function isStaticProperty(node) {
403 return node.type === 'Identifier' || node.type === 'Literal';
404}
405
406function resolveProperty(node) {
407 return node.type === 'Identifier' ? node.name : node.value;
408}
409
410function 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
418function nearestScope(node) {
419 do {
420 var scope = scan.scope(node);
421 if (scope) return scope;
422 } while ((node = node.parent));
423}
424
425function 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}