rollup-plugin-inject.cjs.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. 'use strict';
  2. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  3. var rollupPluginutils = require('rollup-pluginutils');
  4. var path = require('path');
  5. var estreeWalker = require('estree-walker');
  6. var MagicString = _interopDefault(require('magic-string'));
  7. const escape = str => {
  8. return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
  9. };
  10. const isReference = (node, parent) => {
  11. if (node.type === "MemberExpression") {
  12. return !node.computed && isReference(node.object, node);
  13. }
  14. if (node.type === "Identifier") {
  15. // TODO is this right?
  16. if (parent.type === "MemberExpression") return parent.computed || node === parent.object;
  17. // disregard the `bar` in { bar: foo }
  18. if (parent.type === "Property" && node !== parent.value) return false;
  19. // disregard the `bar` in `class Foo { bar () {...} }`
  20. if (parent.type === "MethodDefinition") return false;
  21. // disregard the `bar` in `export { foo as bar }`
  22. if (parent.type === "ExportSpecifier" && node !== parent.local) return;
  23. return true;
  24. }
  25. };
  26. const flatten = node => {
  27. const parts = [];
  28. while (node.type === "MemberExpression") {
  29. parts.unshift(node.property.name);
  30. node = node.object;
  31. }
  32. const name = node.name;
  33. parts.unshift(name);
  34. return { name, keypath: parts.join(".") };
  35. };
  36. function inject(options) {
  37. if (!options) throw new Error("Missing options");
  38. const filter = rollupPluginutils.createFilter(options.include, options.exclude);
  39. let modules = options.modules;
  40. if (!modules) {
  41. modules = Object.assign({}, options);
  42. delete modules.include;
  43. delete modules.exclude;
  44. delete modules.sourceMap;
  45. delete modules.sourcemap;
  46. }
  47. const modulesMap = new Map(Object.entries(modules));
  48. // Fix paths on Windows
  49. if (path.sep !== "/") {
  50. modulesMap.forEach((mod, key) => {
  51. modulesMap.set(
  52. key,
  53. Array.isArray(mod) ? [mod[0].split(path.sep).join("/"), mod[1]] : mod.split(path.sep).join("/")
  54. );
  55. });
  56. }
  57. const firstpass = new RegExp(
  58. `(?:${Array.from(modulesMap.keys())
  59. .map(escape)
  60. .join("|")})`,
  61. "g"
  62. );
  63. const sourceMap = options.sourceMap !== false && options.sourcemap !== false;
  64. return {
  65. name: "inject",
  66. transform(code, id) {
  67. if (!filter(id)) return null;
  68. if (code.search(firstpass) === -1) return null;
  69. if (path.sep !== "/") id = id.split(path.sep).join("/");
  70. let ast = null;
  71. try {
  72. ast = this.parse(code);
  73. } catch (err) {
  74. this.warn({
  75. code: "PARSE_ERROR",
  76. message: `rollup-plugin-inject: failed to parse ${id}. Consider restricting the plugin to particular files via options.include`
  77. });
  78. }
  79. if (!ast) {
  80. return null;
  81. }
  82. // analyse scopes
  83. let scope = rollupPluginutils.attachScopes(ast, "scope");
  84. const imports = new Set();
  85. ast.body.forEach(node => {
  86. if (node.type === "ImportDeclaration") {
  87. node.specifiers.forEach(specifier => {
  88. imports.add(specifier.local.name);
  89. });
  90. }
  91. });
  92. const magicString = new MagicString(code);
  93. const newImports = new Map();
  94. function handleReference(node, name, keypath) {
  95. let mod = modulesMap.get(keypath);
  96. if (mod && !imports.has(name) && !scope.contains(name)) {
  97. if (typeof mod === "string") mod = [mod, "default"];
  98. // prevent module from importing itself
  99. if (mod[0] === id) return;
  100. const hash = `${keypath}:${mod[0]}:${mod[1]}`;
  101. const importLocalName =
  102. name === keypath ? name : rollupPluginutils.makeLegalIdentifier(`$inject_${keypath}`);
  103. if (!newImports.has(hash)) {
  104. if (mod[1] === "*") {
  105. newImports.set(hash, `import * as ${importLocalName} from '${mod[0]}';`);
  106. } else {
  107. newImports.set(hash, `import { ${mod[1]} as ${importLocalName} } from '${mod[0]}';`);
  108. }
  109. }
  110. if (name !== keypath) {
  111. magicString.overwrite(node.start, node.end, importLocalName, {
  112. storeName: true
  113. });
  114. }
  115. return true;
  116. }
  117. }
  118. estreeWalker.walk(ast, {
  119. enter(node, parent) {
  120. if (sourceMap) {
  121. magicString.addSourcemapLocation(node.start);
  122. magicString.addSourcemapLocation(node.end);
  123. }
  124. if (node.scope) scope = node.scope;
  125. // special case – shorthand properties. because node.key === node.value,
  126. // we can't differentiate once we've descended into the node
  127. if (node.type === "Property" && node.shorthand) {
  128. const name = node.key.name;
  129. handleReference(node, name, name);
  130. return this.skip();
  131. }
  132. if (isReference(node, parent)) {
  133. const { name, keypath } = flatten(node);
  134. const handled = handleReference(node, name, keypath);
  135. if (handled) return this.skip();
  136. }
  137. },
  138. leave(node) {
  139. if (node.scope) scope = scope.parent;
  140. }
  141. });
  142. if (newImports.size === 0) {
  143. return {
  144. code,
  145. ast,
  146. map: sourceMap ? magicString.generateMap({ hires: true }) : null
  147. };
  148. }
  149. const importBlock = Array.from(newImports.values()).join("\n\n");
  150. magicString.prepend(importBlock + "\n\n");
  151. return {
  152. code: magicString.toString(),
  153. map: sourceMap ? magicString.generateMap({ hires: true }) : null
  154. };
  155. }
  156. };
  157. }
  158. module.exports = inject;