loader.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. Copyright (c) 2014 Google Inc. All rights reserved.
  3. Copyright (c) 2012-2013 Johannes Ewald.
  4. Use of this source code is governed by the MIT License, available in this package's LICENSE file
  5. or at http://opensource.org/licenses/MIT.
  6. */
  7. const _ = require('lodash');
  8. const fs = require('fs');
  9. const Module = require('module');
  10. const originalWrapper = Module.wrapper.slice(0);
  11. const requizzleWrappers = {
  12. extras: require('./wrappers/extras'),
  13. requirePaths: require('./wrappers/requirepaths'),
  14. strict: require('./wrappers/strict')
  15. };
  16. function wrap(wrappers, script) {
  17. return wrappers[0] + script + wrappers[1];
  18. }
  19. function replaceWrapper(wrapperObj) {
  20. const joiner = '\n';
  21. const before = wrapperObj.before.join(joiner);
  22. const after = wrapperObj.after.join(joiner);
  23. const wrappers = [
  24. originalWrapper[0] + before,
  25. after + originalWrapper[1]
  26. ];
  27. Module.wrap = wrap.bind(null, wrappers);
  28. }
  29. function restoreWrapper() {
  30. Module.wrap = wrap.bind(null, originalWrapper);
  31. }
  32. function createModule(targetPath, parentModule, moduleCache) {
  33. moduleCache[targetPath] = moduleCache[targetPath] || new Module(targetPath, parentModule);
  34. return moduleCache[targetPath];
  35. }
  36. /**
  37. * Wrapper for `require()` to prevent the target module's dependencies from being swizzled.
  38. *
  39. * @param {!Module} targetModule - The module that is being swizzled.
  40. * @param {!function} nodeRequire - The original `require()` method for the target module.
  41. * @param {!string} filepath - The value passed to `require()`.
  42. * @return {!Module} The requested module dependency.
  43. */
  44. function requireProxy(targetModule, nodeRequire, filepath) {
  45. restoreWrapper();
  46. targetModule.require = nodeRequire;
  47. return nodeRequire.call(targetModule, filepath);
  48. }
  49. /**
  50. * Wrapper for `require()` to swizzle the target module's dependencies, using the same settings as
  51. * the target module.
  52. *
  53. * @param {!Module} targetModule - The module that is being swizzled.
  54. * @param {!Object} opts - The Requizzle options object.
  55. * @param {!string} filepath - The value passed to `require()`.
  56. * @return {!Module} The requested module dependency.
  57. */
  58. function infectProxy(targetModule, cache, opts, filepath) {
  59. let moduleExports;
  60. // loaded here to avoid circular dependencies
  61. const Requizzle = require('./requizzle');
  62. let requizzle;
  63. opts = _.clone(opts);
  64. opts.parent = targetModule;
  65. requizzle = new Requizzle(opts, cache);
  66. moduleExports = requizzle.requizzle(filepath);
  67. return moduleExports;
  68. }
  69. exports.load = function load(targetPath, parentModule, wrapper, cache, options) {
  70. let nodeRequire;
  71. let targetModule;
  72. // Handle circular requires, and avoid reloading modules unnecessarily
  73. if (cache.module[targetPath]) {
  74. return cache.module[targetPath];
  75. }
  76. targetModule = createModule(targetPath, parentModule, cache.module);
  77. nodeRequire = targetModule.require;
  78. if (options.infect) {
  79. targetModule.require = filepath => infectProxy(targetModule, cache, options, filepath);
  80. } else {
  81. targetModule.require = filepath => requireProxy(targetModule, nodeRequire, filepath);
  82. }
  83. // update the wrapper before we load the target module
  84. replaceWrapper(wrapper);
  85. targetModule.load(targetModule.id);
  86. // make sure the wrapper is restored even if the target module doesn't load any dependencies
  87. restoreWrapper();
  88. return targetModule;
  89. };
  90. /**
  91. * Check whether the entire module includes a `'use strict'` declaration.
  92. *
  93. * @param {string} src - The source file to check.
  94. * @return {boolean} Set to `true` if the module includes a `use strict` declaration.
  95. */
  96. function detectStrictMode(src) {
  97. return (/^\s*(?:["']use strict["'])[ \t]*(?:[\r\n]|;)/g).test(src);
  98. }
  99. function loadSource(targetPath, sourceCache) {
  100. if (sourceCache[targetPath] === undefined) {
  101. sourceCache[targetPath] = fs.readFileSync(targetPath, 'utf8');
  102. }
  103. return sourceCache[targetPath];
  104. }
  105. exports.createWrapper = function createWrapper(targetPath, parentModule, cache, options) {
  106. let src;
  107. const wrapperObject = {
  108. before: [],
  109. after: []
  110. };
  111. function add(wrapperFunctions, opts) {
  112. const params = [targetPath, parentModule, opts];
  113. ['before', 'after'].forEach(item => {
  114. const result = wrapperFunctions[item].apply(null, params);
  115. if (result) {
  116. wrapperObject[item].push(result);
  117. }
  118. });
  119. }
  120. // Preserve the module's `use strict` declaration if present
  121. src = loadSource(targetPath, cache.source);
  122. if (detectStrictMode(src) === true) {
  123. add(requizzleWrappers.strict);
  124. }
  125. if (options.requirePaths) {
  126. add(requizzleWrappers.requirePaths, options.requirePaths);
  127. }
  128. if (options.extras) {
  129. add(requizzleWrappers.extras, options.extras);
  130. }
  131. return wrapperObject;
  132. };