/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ 'use strict'; /** * This transform inlines top-level require(...) aliases with to enable lazy * loading of dependencies. It is able to inline both single references and * child property references. * * For instance: * var Foo = require('foo'); * f(Foo); * * Will be transformed into: * f(require('foo')); * * When the assigment expression has a property access, it will be inlined too, * keeping the property. For instance: * var Bar = require('foo').bar; * g(Bar); * * Will be transformed into: * g(require('foo').bar); * * Destructuring also works the same way. For instance: * const {Baz} = require('foo'); * h(Baz); * * Is also successfully inlined into: * g(require('foo').Baz); */ module.exports = babel => { return { name: 'inline-requires', visitor: { Program: { exit(path, state) { var ignoredRequires = {}; var inlineableCalls = {require: true}; if (state.opts) { if (state.opts.ignoredRequires) { state.opts.ignoredRequires.forEach(function(name) { ignoredRequires[name] = true; }); } if (state.opts.inlineableCalls) { state.opts.inlineableCalls.forEach(function(name) { inlineableCalls[name] = true; }); } } path.scope.crawl(); path.traverse( {CallExpression: call.bind(null, babel)}, { ignoredRequires: ignoredRequires, inlineableCalls: inlineableCalls, } ); }, }, }, }; }; function call(babel, path, state) { var declaratorPath = inlineableAlias(path, state) || inlineableMemberAlias(path, state); var declarator = declaratorPath && declaratorPath.node; if (declarator) { var init = declarator.init; var name = declarator.id && declarator.id.name; var binding = declaratorPath.scope.getBinding(name); var constantViolations = binding.constantViolations; var thrown = false; if (!constantViolations.length) { deleteLocation(init); babel.traverse(init, { noScope: true, enter: path => deleteLocation(path.node), }); binding.referencePaths.forEach(ref => { try { ref.replaceWith(init); } catch (err) { thrown = true; } }); // If an error was thrown, it's most likely due to an invalid replacement // happening (e.g. trying to replace a type annotation). It would usually // be OK to ignore it, but to be safe, we will avoid removing the initial // require. if (!thrown) { declaratorPath.remove(); } } } } function deleteLocation(node) { delete node.start; delete node.end; delete node.loc; } function inlineableAlias(path, state) { const isValid = isInlineableCall(path.node, state) && path.parent.type === 'VariableDeclarator' && path.parent.id.type === 'Identifier' && path.parentPath.parent.type === 'VariableDeclaration' && path.parentPath.parentPath.parent.type === 'Program'; return isValid ? path.parentPath : null; } function inlineableMemberAlias(path, state) { const isValid = isInlineableCall(path.node, state) && path.parent.type === 'MemberExpression' && path.parentPath.parent.type === 'VariableDeclarator' && path.parentPath.parent.id.type === 'Identifier' && path.parentPath.parentPath.parent.type === 'VariableDeclaration' && path.parentPath.parentPath.parentPath.parent.type === 'Program'; return isValid ? path.parentPath.parentPath : null; } function isInlineableCall(node, state) { const isInlineable = node.type === 'CallExpression' && node.callee.type === 'Identifier' && state.inlineableCalls.hasOwnProperty(node.callee.name) && node['arguments'].length >= 1; // require('foo'); const isStandardCall = isInlineable && node['arguments'][0].type === 'StringLiteral' && !state.ignoredRequires.hasOwnProperty(node['arguments'][0].value); // require(require.resolve('foo')); const isRequireResolveCall = isInlineable && node['arguments'][0].type === 'CallExpression' && node['arguments'][0].callee.type === 'MemberExpression' && node['arguments'][0].callee.object.type === 'Identifier' && state.inlineableCalls.hasOwnProperty(node['arguments'][0].callee.object.name) && node['arguments'][0].callee.property.type === 'Identifier' && node['arguments'][0].callee.property.name === 'resolve' && node['arguments'][0]['arguments'].length >= 1 && node['arguments'][0]['arguments'][0].type === 'StringLiteral' && !state.ignoredRequires.hasOwnProperty(node['arguments'][0]['arguments'][0].value); return isStandardCall || isRequireResolveCall; }