index.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. var undeclaredIdentifiers = require('undeclared-identifiers');
  2. var through = require('through2');
  3. var merge = require('xtend');
  4. var parse = require('acorn-node').parse;
  5. var path = require('path');
  6. var isAbsolute = path.isAbsolute || require('path-is-absolute');
  7. var processPath = require.resolve('process/browser.js');
  8. var isbufferPath = require.resolve('is-buffer')
  9. var combineSourceMap = require('combine-source-map');
  10. function getRelativeRequirePath(fullPath, fromPath) {
  11. var relpath = path.relative(path.dirname(fromPath), fullPath);
  12. // If fullPath is in the same directory or a subdirectory of fromPath,
  13. // relpath will result in something like "index.js", "src/abc.js".
  14. // require() needs "./" prepended to these paths.
  15. if (!/^\./.test(relpath) && !isAbsolute(relpath)) {
  16. relpath = "./" + relpath;
  17. }
  18. // On Windows: Convert path separators to what require() expects
  19. if (path.sep === '\\') {
  20. relpath = relpath.replace(/\\/g, '/');
  21. }
  22. return relpath;
  23. }
  24. var defaultVars = {
  25. process: function (file) {
  26. var relpath = getRelativeRequirePath(processPath, file);
  27. return 'require(' + JSON.stringify(relpath) + ')';
  28. },
  29. global: function () {
  30. return 'typeof global !== "undefined" ? global : '
  31. + 'typeof self !== "undefined" ? self : '
  32. + 'typeof window !== "undefined" ? window : {}'
  33. ;
  34. },
  35. 'Buffer.isBuffer': function (file) {
  36. var relpath = getRelativeRequirePath(isbufferPath, file);
  37. return 'require(' + JSON.stringify(relpath) + ')';
  38. },
  39. Buffer: function () {
  40. return 'require("buffer").Buffer';
  41. },
  42. setImmediate: function () {
  43. return 'require("timers").setImmediate';
  44. },
  45. clearImmediate: function () {
  46. return 'require("timers").clearImmediate';
  47. },
  48. __filename: function (file, basedir) {
  49. var relpath = path.relative(basedir, file);
  50. // standardize path separators, use slash in Windows too
  51. if ( path.sep === '\\' ) {
  52. relpath = relpath.replace(/\\/g, '/');
  53. }
  54. var filename = '/' + relpath;
  55. return JSON.stringify(filename);
  56. },
  57. __dirname: function (file, basedir) {
  58. var relpath = path.relative(basedir, file);
  59. // standardize path separators, use slash in Windows too
  60. if ( path.sep === '\\' ) {
  61. relpath = relpath.replace(/\\/g, '/');
  62. }
  63. var dir = path.dirname('/' + relpath );
  64. return JSON.stringify(dir);
  65. }
  66. };
  67. module.exports = function (file, opts) {
  68. if (/\.json$/i.test(file)) return through();
  69. if (!opts) opts = {};
  70. var basedir = opts.basedir || '/';
  71. var vars = merge(defaultVars, opts.vars);
  72. var varNames = Object.keys(vars).filter(function(name) {
  73. return typeof vars[name] === 'function';
  74. });
  75. var quick = RegExp(varNames.map(function (name) {
  76. return '\\b' + name + '\\b';
  77. }).join('|'));
  78. var chunks = [];
  79. return through(write, end);
  80. function write (chunk, enc, next) { chunks.push(chunk); next() }
  81. function end () {
  82. var self = this;
  83. var source = Buffer.isBuffer(chunks[0])
  84. ? Buffer.concat(chunks).toString('utf8')
  85. : chunks.join('')
  86. ;
  87. source = source
  88. .replace(/^\ufeff/, '')
  89. .replace(/^#![^\n]*\n/, '\n');
  90. if (opts.always !== true && !quick.test(source)) {
  91. this.push(source);
  92. this.push(null);
  93. return;
  94. }
  95. try {
  96. var undeclared = opts.always
  97. ? { identifiers: varNames, properties: [] }
  98. : undeclaredIdentifiers(parse(source), { wildcard: true })
  99. ;
  100. }
  101. catch (err) {
  102. var e = new SyntaxError(
  103. (err.message || err) + ' while parsing ' + file
  104. );
  105. e.type = 'syntax';
  106. e.filename = file;
  107. return this.emit('error', e);
  108. }
  109. var globals = {};
  110. varNames.forEach(function (name) {
  111. if (!/\./.test(name)) return;
  112. var parts = name.split('.')
  113. var prop = undeclared.properties.indexOf(name)
  114. if (prop === -1 || countprops(undeclared.properties, parts[0]) > 1) return;
  115. var value = vars[name](file, basedir);
  116. if (!value) return;
  117. globals[parts[0]] = '{'
  118. + JSON.stringify(parts[1]) + ':' + value + '}';
  119. self.emit('global', name);
  120. });
  121. varNames.forEach(function (name) {
  122. if (/\./.test(name)) return;
  123. if (globals[name]) return;
  124. if (undeclared.identifiers.indexOf(name) < 0) return;
  125. var value = vars[name](file, basedir);
  126. if (!value) return;
  127. globals[name] = value;
  128. self.emit('global', name);
  129. });
  130. this.push(closeOver(globals, source, file, opts));
  131. this.push(null);
  132. }
  133. };
  134. module.exports.vars = defaultVars;
  135. function closeOver (globals, src, file, opts) {
  136. var keys = Object.keys(globals);
  137. if (keys.length === 0) return src;
  138. var values = keys.map(function (key) { return globals[key] });
  139. // we double-wrap the source in IIFEs to prevent code like
  140. // (function(Buffer){ const Buffer = null }())
  141. // which causes a parse error.
  142. var wrappedSource = '(function (){\n' + src + '\n}).call(this)';
  143. if (keys.length <= 3) {
  144. wrappedSource = '(function (' + keys.join(',') + '){'
  145. + wrappedSource + '}).call(this,' + values.join(',') + ')'
  146. ;
  147. }
  148. else {
  149. // necessary to make arguments[3..6] still work for workerify etc
  150. // a,b,c,arguments[3..6],d,e,f...
  151. var extra = [ '__argument0', '__argument1', '__argument2', '__argument3' ];
  152. var names = keys.slice(0,3).concat(extra).concat(keys.slice(3));
  153. values.splice(3, 0,
  154. 'arguments[3]','arguments[4]',
  155. 'arguments[5]','arguments[6]'
  156. );
  157. wrappedSource = '(function (' + names.join(',') + '){'
  158. + wrappedSource + '}).call(this,' + values.join(',') + ')';
  159. }
  160. // Generate source maps if wanted. Including the right offset for
  161. // the wrapped source.
  162. if (!opts.debug) {
  163. return wrappedSource;
  164. }
  165. var sourceFile = path.relative(opts.basedir, file)
  166. .replace(/\\/g, '/');
  167. var sourceMap = combineSourceMap.create().addFile(
  168. { sourceFile: sourceFile, source: src},
  169. { line: 1 });
  170. return combineSourceMap.removeComments(wrappedSource) + "\n"
  171. + sourceMap.comment();
  172. }
  173. function countprops (props, name) {
  174. return props.filter(function (prop) {
  175. return prop.slice(0, name.length + 1) === name + '.';
  176. }).length;
  177. }