/* Copyright (c) 2014 Google Inc. All rights reserved. Copyright (c) 2012-2013 Johannes Ewald. Use of this source code is governed by the MIT License, available in this package's LICENSE file or at http://opensource.org/licenses/MIT. */ const _ = require('lodash'); const fs = require('fs'); const Module = require('module'); const originalWrapper = Module.wrapper.slice(0); const requizzleWrappers = { extras: require('./wrappers/extras'), requirePaths: require('./wrappers/requirepaths'), strict: require('./wrappers/strict') }; function wrap(wrappers, script) { return wrappers[0] + script + wrappers[1]; } function replaceWrapper(wrapperObj) { const joiner = '\n'; const before = wrapperObj.before.join(joiner); const after = wrapperObj.after.join(joiner); const wrappers = [ originalWrapper[0] + before, after + originalWrapper[1] ]; Module.wrap = wrap.bind(null, wrappers); } function restoreWrapper() { Module.wrap = wrap.bind(null, originalWrapper); } function createModule(targetPath, parentModule, moduleCache) { moduleCache[targetPath] = moduleCache[targetPath] || new Module(targetPath, parentModule); return moduleCache[targetPath]; } /** * Wrapper for `require()` to prevent the target module's dependencies from being swizzled. * * @param {!Module} targetModule - The module that is being swizzled. * @param {!function} nodeRequire - The original `require()` method for the target module. * @param {!string} filepath - The value passed to `require()`. * @return {!Module} The requested module dependency. */ function requireProxy(targetModule, nodeRequire, filepath) { restoreWrapper(); targetModule.require = nodeRequire; return nodeRequire.call(targetModule, filepath); } /** * Wrapper for `require()` to swizzle the target module's dependencies, using the same settings as * the target module. * * @param {!Module} targetModule - The module that is being swizzled. * @param {!Object} opts - The Requizzle options object. * @param {!string} filepath - The value passed to `require()`. * @return {!Module} The requested module dependency. */ function infectProxy(targetModule, cache, opts, filepath) { let moduleExports; // loaded here to avoid circular dependencies const Requizzle = require('./requizzle'); let requizzle; opts = _.clone(opts); opts.parent = targetModule; requizzle = new Requizzle(opts, cache); moduleExports = requizzle.requizzle(filepath); return moduleExports; } exports.load = function load(targetPath, parentModule, wrapper, cache, options) { let nodeRequire; let targetModule; // Handle circular requires, and avoid reloading modules unnecessarily if (cache.module[targetPath]) { return cache.module[targetPath]; } targetModule = createModule(targetPath, parentModule, cache.module); nodeRequire = targetModule.require; if (options.infect) { targetModule.require = filepath => infectProxy(targetModule, cache, options, filepath); } else { targetModule.require = filepath => requireProxy(targetModule, nodeRequire, filepath); } // update the wrapper before we load the target module replaceWrapper(wrapper); targetModule.load(targetModule.id); // make sure the wrapper is restored even if the target module doesn't load any dependencies restoreWrapper(); return targetModule; }; /** * Check whether the entire module includes a `'use strict'` declaration. * * @param {string} src - The source file to check. * @return {boolean} Set to `true` if the module includes a `use strict` declaration. */ function detectStrictMode(src) { return (/^\s*(?:["']use strict["'])[ \t]*(?:[\r\n]|;)/g).test(src); } function loadSource(targetPath, sourceCache) { if (sourceCache[targetPath] === undefined) { sourceCache[targetPath] = fs.readFileSync(targetPath, 'utf8'); } return sourceCache[targetPath]; } exports.createWrapper = function createWrapper(targetPath, parentModule, cache, options) { let src; const wrapperObject = { before: [], after: [] }; function add(wrapperFunctions, opts) { const params = [targetPath, parentModule, opts]; ['before', 'after'].forEach(item => { const result = wrapperFunctions[item].apply(null, params); if (result) { wrapperObject[item].push(result); } }); } // Preserve the module's `use strict` declaration if present src = loadSource(targetPath, cache.source); if (detectStrictMode(src) === true) { add(requizzleWrappers.strict); } if (options.requirePaths) { add(requizzleWrappers.requirePaths, options.requirePaths); } if (options.extras) { add(requizzleWrappers.extras, options.extras); } return wrapperObject; };